References:
1. 开-闭原则
定义
开-闭原则就是让设计对拓展开放,对修改关闭。
理解
开-闭原则的本质是指,当一个设计中增加新的模块时,不需要修改现有的模块。
设计的核心部分并不是用来应对用户的需求变化的,这部分不能因为用户的需求变化而在发生变化。
当用户的需求发生变更时,一方面,如果核心代码中存在各种各样的大量具体实现,想去全部重写这些具体实现的工作量是巨大的,另一方面,修改代码总是会带来未知的风险,当模块间的联系千丝万缕时,修改任何一个模块都得小心翼翼,否则很可能发生改好 1 个 bug,多出 3 个 bug 的情况,增加系统维护的难度。
因此,我们总是希望核心模块的代码尽量稳定。
开-闭原则的好处
如果设计遵守开-闭原则,那么这个设计一定是易维护的。
2. 面向抽象原则(依赖倒置原则)
定义
面向抽象原则是指,当设计一个类时,要让该类面向抽象类或接口,而不是面向具体的类编程。即所设计类中的重要数据是抽象类或接口声明的变量,而不是具体类声明的变量。
简单来说,就是找出通用特性,并将其抽象和封装出来,使修改远离核心代码。
理解
引用课本提供的示例,假设我们已经有了一个Circle类(圆类),该类创建的对象circle可以调用getArea()方法计算圆的面积。代码如下:
public class Circle{
double r;
Circle(double r){
this.r = r;
}
public double getArea(){
return 3.14 * r * r;
}
}
另外,我们还有一个Pillar类(柱类),该类的对象调用getVolume()方法可以计算柱体的体积。代码如下:
public class Pillar{
Circle bottom;
double height;
Pillar(Circle bottom, double height){
this.bottom = bottom;
this.height = height;
}
public double getVolume(){
return bottom.getArea() * height;
}
}
可以看出,Pillar类的成员变量bottom是用具体类Circle声明的变量,因此现在的Pillar类只能计算圆柱的体积,作为圆柱被使用。如果我们想要改为计算底面为三角形或其他形状的柱体的体积,就需要修改Pillar类具体实现的代码。为了满足需求的变换,我们需要深入核心源代码,做大量改动,即不满足开-闭原则。这里就需要引出这部分的主角——抽象:
抽象的意思是:从一些事物中抽取出共同的、本质性的特征。
这时我们注意到,如果Pillar类的底是某个具体类声明的变量,Pillar类就依赖该具体类,难以应对用户需求的变化。我们还注意到,柱体计算体积时,可以被抽象出来的通用特性是底面的面积。我们并不应该关心柱体底面的形状,而应该只关心底面图形是否具有计算面积的方法。
这时,我们可以编写一个抽象类(或接口)Geometry(几何图形),其中定义了一个抽象的getArea()方法。代码如下:
public abstract class Geometry{
public abstract double getArea();
}
面向抽象的好处
- (重要)面向抽象编程可以将系统中经常变化的部分封装在抽象里,保持核心模块的稳定。
- 面向抽象编程可以让核心模块开发者从非核心模块的实现细节中解放出来,将这些非核心模块的实现细节留在后期或者留给其他人。
3. 高内聚-低耦合原则
定义
高内聚:类中的方法是一组相关的行为
低耦合:尽量不要让一个类含有太多其他类的实例引用
理解
高内聚(单一职责原则)
一个类只负责一个功能领域中的相应职责,也可以定义为:一个类应该只有一个发生变化的原因。
低耦合(迪米特法则)
一个类只和应该有直接依赖关系的类建立依赖关系,尽量不要让一个类含有太多其他类的实例引用,避免修改系统的其中一部分会影响到其他部分。
白话文:让类只和它的直接朋友交谈,而不跟陌生人说话。
高内聚-低耦合的好处
保证系统架构的清晰性,便于维护、复用、扩展。
4. 多用组合少用继承原则(合成复用原则)
定义
顾名思义,尽量先试用组合或聚合等关联关系来实现方法复用,其次才考虑使用继承关系。
理解
假设我们需要在A中调用B的方法,我们可以使用继承和组合两种方法:
继承
A extends B
is-a
优点:
- 子类可以重写父类的方法,易于修改或拓展被复用的方法
缺点:
-
子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。
以前面的柱体Pillar类和圆Circle类为例,如果Pillar类想要使用Circle类中的getArea()方法,可以选择继承Circle类。一旦程序开始运行,就无法把继承来的getArea()方法改为计算三角形的面积。
-
子类和父类的关系是强耦合关系,父类的方法被更改时,子类的方法也必须做出改变,不满足对修改关闭。
-
父类的内部细节对于子类而言是可见的。
组合
B作为A的成员变量,委托对象b调用其方法
has-a
优点:
- 当前对象所包含对象方法的细节对当前对象不可见。
- 弱耦合,对B的代码进行修改时,无需同时修改A的代码。
- A类对象可以在运行时动态指定B类对象。
缺点:
- 容易导致系统中的对象过多。
- 为了能组合多个对象,必须仔细地对接口进行定义。
多用组合少用继承的好处
使系统的类之间尽量是低耦合关系。