软件构造体会(四) 关于五大原则的复习总结
我们在软件构造第八节以及第九节的课堂中学习了JAVA中的五大原则,即SOLID原则,其中lsp原则我们接触的比较多而且接触的还比较早,剩下四个也都有着不同的特点,接下来我们将主要对这些原则的特点,以及在各个场景的适用情况,进行详细的阐释,将我在复习中的心得与一些感悟总结如下
一.单一责任原则(SRP)
内容:类或模块只有一个职责。也就是说,模块、类或方法不应该承担太多工作。
这个原则要求我们当设计一个类时不应该把它设计成一个大的、完整的类,而应该把它设计成一个小的、单功能的类。如果一个类有两个或更多不相关的函数,那么我们就说它违反了单一责任的原则,应该用一个函数和更细的划分为多个类。
我们课件中指出,如果书写的的类过于庞大,则有可能会导致功能实现的降低,如果一个类包含了多个责任,那么将引起不良后果:引入额外的包,占据资源,导致频繁的重新配置、部署等。
在编写程序时我们可以简单的设计一个广泛的类,然后随着业务的发展对其进行重构,而不是遵循严格的规则。
我们在写代码时可以按照以下建议进行代码重构或设计:
1.当一个类过于依赖另一个类,或者直接依赖于它的代码过于复杂不适合高内聚、低耦合的设计理念时可以考虑拆分代码。
2.当类的名称与实际函数无关或没有任何关联时,可以将其更精细地分割并与不相关的函数隔离。
3.当一个类的代码函数太多会影响代码的可读性和维护时,我们可以在方法级别拆分代码。
如下面的代码:
interface Modem { //反例
public void dial(String pno);
public void hangup();
public void send(char c);
public char recv();
}
interface DataChannel {//符合SRP原则的代码
public void send(char c);
public char recv();
}
interface Connection {
public void dial(String phn);
public char hangup();
}
二.开闭原则(OCP):
内容:软件实体(模块、类、函数等)应该是对扩展开放,对修改关闭,简单说就是添加特性应该是现有代码的扩展,而不是对现有代码的修改。
我们可以知道开闭原则的目的是使代码变为可扩展的,避免更改软件中现有代码的风险。可扩展性要求理解在以后可能的扩展点:
1. 如果它是一个业务驱动的系统,需要的前提下充分理解业务需求,我们可以找到相应的扩展点,如果太多的不确定性,需求变化太快,则可以对于一些比较确定的,短期内就可能会扩展,可能在短期内,通过扩展点的设计,可以显著提高开发的稳定性和效率进行设计代码。
2. 对于通用技术开发,如开发通用框架、组件和库,您需要考虑用户将如何使用框架,哪些扩展点保留用于功能升级,以及版本之间的兼容性问题。
3.即使您对系统的业务或技术框架有很好的理解,也没有必要设计所有的扩展点。为每一个可能的未来更改留出一个扩展点也会给系统增加很多复杂性,并使它需要大量的工作来实现。应该考虑开发成本、影响范围和实际效益(包括时间和人员成本)。
public void drawShape(Shape s) {//反例
if (s.m_type==1)
drawRectangle(s);
else if (s.m_type==2)
drawCircle(s);
}
public void drawCircle(Circle r){
....}
public void drawRectangle(Rectangle r){
....}
}
class GraphicEditor {//正确做法
public void drawShape(Shape s) {
s.draw();
}
}
class Shape {
abstract void draw();
}
class Rectangle extends Shape {
public void draw() {
// draw the rectangle
}
}
三.里氏替换原则(LSP):
定义:子类对象可以在任何地方替换父类对象,并确保原始程序逻辑行为和正确性不被破坏。若A是B的子类型,则B类型的对象可以替换为A类型的对象,使用父类引用指针的程序/函数则能够使用派生类的对象。
可以通过使用面向对象编程的多态性来实现。多态性和里氏替换原则是相似的,但是他们的关注点是不一样的。多态是面向对象编程的特点之一,里氏替换是一种设计原则,用于指导如何继承设计子类,在设计子类时确保替换父类,不改变原程序逻辑,不破坏原程序的正确性。
这个实现可以理解为:当一个子类被设计时,它应该遵循父类的行为约定。父类定义了方法的行为。子类可以改变方法的内部实现逻辑,但它不能改变方法的行为约定,比如接口/方法声明做什么,关于参数值、返回值、异常的约定,甚至是注释中列出的任何特殊指令。
class Rectangle {//如下便为一个例子
int h, w;
Rectangle(int h, int w) {
this.h = h;
this.w = w;
}
void scale(int factor) {
w = w * factor;
h = h * factor;
}
void setWidth(int neww) {
w = neww;
}
}
class Square extends Rectangle {
Square(int w) {
super(w, w);
}
四.接口隔离原理(ISP):
内容:不应该强迫客户端依赖于它不需要的接口
接口隔离原理可以采用如下方法:
-
在接口的情况下,如果一个接口假设了一个与它无关的接口定义,则称该接口违反了接口隔离原则,可以去掉无关的接口。
-
对于通用功能,应该对程序细分特性,并根据需要添加它们,而不是定义一个大型而全面的接口,让子类强制实现。
interface Worker {//反例
void work();
void eat();
}
interface Workable {
public void work();
}
ManWorker implements Worker{
void work(){…};
void eat(){…};
}
RobotWorker implements Worker{
void work(){…};
void eat(){//Not Appliciable
for a RobotWorker};
}
interface Feedable {
public void eat();
}
interface Workable {//修改后
public void work();
}
interface Feedable {
public void eat();
}
ManWorker implements Workable,Feedable{
void work(){…};
void eat(){…};
}
RobotWorker implements Workable{
void work(){…};
}
五.依赖倒置原则(DIP):
说明:高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象来互相依赖。除此之外,抽象不要依赖具体实现细节,具体实现细节依赖抽象
这里的高层模块,从代码角度来说就是调用者,底层模块就是被调用者。即调用者不要依赖于具体的实现,而应该依赖于抽象,如Spring框架中的各种Aware接口,框架依赖于Aware接口给予具体的实现增加功能,具体的实现通过实现接口来获得功能。而具体的实现与框架直接并没有直接耦合。
内容:高级模块不应依赖于低级模块,而是应该通过抽象相互依赖。此外,抽象不应该依赖于实现细节,而实现细节依赖于抽象
从代码的角度来看,这里的高级模块是调用者,低级模块是被调用者。也就是说调用者不应该依赖于具体的实现而应该依赖于抽象。框架依赖Aware接口向具体实现添加功能,具体实现通过实现接口获得功能。该实现并不直接与框架耦合。
public class EmployeeService {//反例
private EmployeeFinder emFinder; //concrete class, not abstract.
//Can access a SQL DB for instance
public Employee findEmployee(…) {
emFinder.findEmployee(…)
}
}
public class EmployeeService {//修改后
private IEmployeeFinder emFinder
//depends on an abstraction, no an implementation
public Employee findEmployee(…) {
emFinder.findEmployee(…)
}
}
上一篇: Java中各种数据结构的遍历
下一篇: Lab1 report