💡设计原则

1. 封装变化

识别应用中变化的部分,把它们和不变的分开。

软件开发中唯一的不变是“变化”。不管我们的应用设计得多好,随着时间的推移必定会成长和变更,比如需求变动(增/删/改功能)、切换合作方或上下游、技术迭代、优化或重构等。大多数的设计原则和设计模式,都着眼于软件中“变化”的主题。

虽然这条原则很简单,但它是每个设计模式的基础,也是代码解耦的前提。当我们将会变化的部分取出,并将其封装起来,这样就可以只修改或扩展这个部分而不影响其它不需要变化的部分。结果就是由代码变更引起的不经意后果会变少,从而系统就更有弹性了。

设计模式
变化的
不变的

工厂模式

对象的创建 实例化具体类

对象的使用

策略模式

策略的内部实现 策略的数目

策略的使用

观察者模式

主题的状态 观察者的数目和类型

2. 针对接口编程

针对接口编程,而不是针对实现编程。

代码针对接口编写,通过多态,就可以和任何实现这个接口的新类一起工作。

“针对接口编程”的真正意思是“针对超类型编程”。针对接口编程不必真的使用接口,要点是通过针对超类型编程来利用多态。也就是说,变量所声明的类型是超类型(通常是抽象类或接口),这样分配给这些变量的对象就可以是超类型的任何具体实现,这意味着类声明时不必知道实际的对象类型。

设计模式
针对接口编程
说明

策略模式

策略接口

一组策略类实现相同的策略接口

观察者模式

主题和观察者都是用接口

主题跟踪实现观察者接口的对象 观察者通过主题接口注册并被通知

3. 优先使用组合

优先使用组合(has-a),而不是继承(is-a)。

设计模式
优先使用组合
类图

策略模式

客户 has-a 算法接口

观察者模式

主题接口 has 观察者接口列表

复用机制

类继承,也称白箱复用,因为父类的内部细节对子类可见。类继承是在编译时静态定义的(所以不能在运行时改变),且可直接使用(因为程序设计语言支持类继承)。白箱复用的形式破坏了封装性,这会让子类和父类的依赖关系非常紧密,进而导致父类的改动会影响到所有子类。一个可用的解决办法是只继承抽象类,因为抽象类通常提供较少的实现。

对象组合,也称黑箱复用,因为被组合对象的内部细节是不可见的。组合要求对象遵守彼此的接口约定,进而要求更仔细地定义接口。黑箱复用的形式(对象只能通过接口访问)并不破坏封装性,只要类型一致就可以在运行时替换被组合的对象,且彼此在实现上也会存在较少的依赖关系。

复用机制
优点
缺点

类继承

  • 方便地改变被复用的实现 override

  • 创建新类更容易

  • 不能在运行时改变

  • 破坏了封装性

  • 子类和父类的强依赖关系限制了灵活性并最终限制了复用性

对象组合

  • 运行时可以改变行为,更具弹性

  • 类的封装性好,彼此的依赖关系较少,更容易职责单一

  • 相对复杂和难以理解(动态的高度参数化的形式)

  • 运行低效(相比编译时的静态定义)

4. 松耦合

尽量让相互交互的对象之间保持松耦合。

松耦合设计会让我们的系统更有弹性,因为对象之间的依赖度很低。

设计模式
松耦合
说明

策略模式

策略的定义 vs 使用

观察者模式

主题 vs 观察者

5. 对修改关闭

对修改关闭,对扩展开放。

6. 依赖倒置

依赖于抽象,不要依赖于具体类。又称:针对抽象编程,而不是针对具体类。

与“针对接口编程”相比,依赖倒置原则更强调“抽象”。依赖倒置中的“倒置”,是倒转了面向对象设计的一般方式,让自上而下的依赖图自己倒转过来了,高层模块和低层模块现在都依赖于抽象。

依赖倒置原则说明,高层组件不应该依赖于低层组件,而且它们都应该依赖于抽象,而不是具体类(当直接实例化一个对象时,依赖的就是具体的类)。这对高层模块和低层模块都适用。

设计模式
依赖于抽象
类图

工厂方法

高层组件 PizzaStore 和低层组件 Pizza 实现都依赖于抽象 Pizza 类。 可以在不操心具体 Pizza 类的情况下设计 PizzaStore,如此,我们就不得不依靠 Factory 将这些具体类从 PizzaStore 中取出。

以下指南,可以帮助我们避免违反依赖倒置原则:

  • 变量不应该持有到具体类的引用,即使用 new 操作符(可以使用工厂来绕开)

  • 类不应该派生自具体类,因为这样就会依赖具体类(可以派生自一个抽象,即接口或抽象类)

  • 方法不应该覆盖其任何基类的已实现方法,因为这样的话基类就不是一个真正适合被继承的抽象(基类中那些已经实现的方法,应该由所有的子类共享)

注意:以上指南尽量遵循,但不要当成任何时候都应该遵循的铁律。我们应该融会贯通,当违反原则时,知道在违反原则,并且有一个很好的理由。

Last updated