大部分程序员编写的程序可以分为三类(应用程序、工具包 、框架),使用设计模式的目的是提高代码的可复用性和可扩展性(灵活性), 但是设计模式在这三类软件中所发挥的效果是不一样的。
很多有经验的程序员会得出“使用了设计模式,反而降低了代码的可读性,增加了复杂度”的结论, 并把这种问题总结为过度设计。 这种总结其实有失偏颇, 因为设计模式所提供的灵活性本身就是有代价的。 很多程序员所编写的商业应用程序, 其问题领域其实相对固定,对代码的灵活性和重用性要求并不高(相对于与工具包和框架而言), 所以应用程序的编写者往往在不了解设计模式的情况下,也可以很好地解决一些灵活性需求和扩展性需求。
具体来说使用设计模式的必要性的程度是逐级递增的:应用程序(Application) < 工具包/类库(ToolKit/Library) < 框架(Framework)
应用程序
工具包
框架
首先需要注意, 广为流传的“组合优于继承” 的说法是一种不严谨的翻译, 其来源如下
众多设计模式包含的2个最核心原则(引自参考书籍1)
- Program to an interface, not an implementation. (面向接口编程,而不是具体的实现)
- Favor object composition over class inheritance.(如果某个场景的代码复用既可以通过类继承实现, 也可以通过对象组合实现, 尽量选择对象组合的设计方式)
第一个原则的好处非常明显: 可以极大程度地减少子系统具体实现之间的相互依赖。
第二个原则则不那么容易理解, 下面展开叙述 。
面向对象设计的过程中, 两个最常用的技巧就是类继承和对象组合,同一个场景下的代码复用,这两个技巧基本上都可以完成。 但是他们有如下的区别:
这里通过汽车的刹车逻辑进行说明。 对于汽车来说, 存在多种不同的型号, 我们会很自然的希望定义一个类 Car 来描述所有汽车通用的刹车行为 brake(), 然后通过某种方式(继承/组合)来为不同的型号的汽车提供不同的刹车行为。
public abstract class Car {
// 也可以将该方法设置成抽象方法, 强迫子类来实现该方法
public void brake() {
// 提供一个默认的刹车实现
...
}
}
public class CarModelA extends Car {
public void brake() {
aStyleBrake();// A 风格的刹车行为
}
}
public class CarModelB extends Car {
public void brake() {
bStyleBrake(); // B 风格的刹车行为
}
}
上述的例子展现了如何通过继承来完成不同型号车辆刹车行为的变化。但是可以注意到, 每一个型号的车的刹车行为是在编译时就确定好的 , 没有办法在运行时刻将 CarModelB 的刹车行为赋予 CarModelA 。
public interface IBrakeBehavior {
public void brake();
}
public class AStyleBrake implements IBrakeBehavior {
public void brake() {
aStyleBrake(); // A 风格的刹车行为
}
}
public class BStyleBrake implements IBrakeBehavior {
public void brake() {
bStyleBrake(); // B 风格的刹车行为
}
}
//通过给下面的类赋予 AStyleBrake 或 BStyleBrake 可以完成不同 Model 的刹车行为的切换
// 同理, 汽车其他的行为(如启动 launch) 也可以用类似的方法实现
// 不同型号的汽车实现, 可以通过赋予不同风格的行为实例来 “组装” 出来的, 也就不需要为 Car 定义不同的子类了
public class Car{
protected IBrakeBehavior brakeBehavior;
public void brake() {
brakeBehavior.brake();
}
public void setBrakeBehavior(final IBrakeBehavior brakeType) {
this.brakeBehavior = brakeType;
}
}
值得注意的是, 上面的刹车行为不一定需要通过接口来实现, 定义一个 BrakeBehaviour 的父类, 然后再定义AStyleBrake , BAStyleBrake 来继承该类, 实现不同的行为, 同样是组合方式的应用。
所以不难发现, 当我们拿类继承和组合在一起进行对比时, 并不是以实现方式中是否有用到类继承而区分的。
我们真正关注的是行为的继承与行为的组合 :需要变化的行为是通过 继承后重写的方式 实现, 还是通过 赋予不同的行为实例 实现。
类继承优点:
类继承缺点:
CarModel( A, B, C, D, E, F, G,H ,I , J )
, 其中前 5 个型号( A、B、C、D、E)
都没有重写父类的刹车方法, 直接使用了父类 Car 提供的默认方法, 后 5 个型号均提供了自己独特的 brake 实现 。 现假设, 我们希望对 Car 中的 brake 方法进行升级改造, 然而,升级改造后的 brake 行为只适用于C,D
, 最早的两种型号A, B 并不兼容升级后的刹车行为。 这样, 我们为了保证 A, B
依旧能正常工作, 就不得不把旧的 brake 实现挪到 A、B
中。 或者, 分别去升级 C、 D、E
中的 brake 方法。对象组合优点:
对象组合缺点: