jack's notebook

软件设计原则

字数统计: 1.3k阅读时长: 4 min
2020/06/09 Share

这篇笔记会讨论一些通用的软件设计原则,绝大部分设计模式也都是基于这些原则的。遵循这些原则能让我们的软件架构更灵活、稳定且易于理解。我们先了解一些基础的设计模式,然后再看看 SOLID 原则。

设计原则

封装变化的内容

找到程序中的变化内容将其与不变的内容区别开。

面向接口进行开发,而不是面向实现

面向接口进行开发,而不是面向对象;依赖于抽象类型,而不是具体类。

判断是否满足这个原则的标准是:我是否能不修改一个类的已有代码,就对类进行扩展。

下面是一种灵活的方式,来设置对象之间的合作关系。

  1. 确定一个对象对另外一个对象的确切要求:它需要执行哪些方法?

  2. 在一个新的接口或抽象类中描述这些方法。

  3. 让被依赖的类实现该接口。

  4. 现在让有需求的类依赖于这个接口,而不依赖于具体的类。

例子:

一个软件开发公司的模拟器,开发一个软件需要不同类型的雇员。

program-to-interface-before

刚开始时,Company 类与具体雇员类紧密耦合。我们可以先确定,公司类并不关心不同的雇员做的具体工作,我们可以在 Company 类内应用多态机制,新建一个 Employee 接口来处理各类雇员对象。

多态是指程序能够检测对象所属的实际类,并在当前上下文不知道其真实类型的情况下调用其实现的能力。

program-to-interface-middle-zh

这样我们的代码简化了,但是我们发现 Company 类还是依赖于具体的雇员类。比如说,我们现在有一个新的游戏公司,它不需要程序员,但是需要一个运维人员,那么 Company 类就需要修改了,为了解决这个问题,我们可以声明一个抽象方法,来指定公司需要的雇员。program-to-interface-after

修改后的 Company 类已经独立于各种 Emploee 类,“可以对该类进行扩展, 并在复用部分公司基类的情况下引入新的公司和雇员类型。 对公司基类进行扩展时无需修改任何依赖于基类的已有代码。”

上面就是 工厂方法 模式的一个示例。

组合优于继承

我们先来回忆下这张图,以及 组合继承 的描述,

relations-zh

组合:对象 A 知道对象 B、由 B 构成而且管理着 B 的生命周期。类 A 依赖于类 B。

继承:类 A 继承类 B 的接口和实现,但是可以对其进行扩展。对象 A 可被视为对象 B。类 A 依赖于类 B。

通俗点理解,组合代表”有“关系,继承代表”是“关系。

如果要使用继承,先考虑会不会引起下面的问题:

  • 子类不能减少超类的接口。

  • 在重写方法时,你需要确保新行为与其基类中的版本兼容。

  • 继承打破了超类的封装。

  • 子类与超类紧密耦合。

  • 通过继承复用代码可能导致平行继承体系的产生。

比如一个 Car 汽车类,它下面又分 ElectricCar 电动车和 Combustion 汽油车,如果电动车和汽油车又有自动驾驶,手动驾驶的分类,那么我们就要衍生 AutopilotElectricCarAutopilotCombustionCar … 如果再加一个混合动力的车,这就会引起第5点的问题 通过继承复用代码可能导致平行继承体系的产生,只要出现两个以上的维度,就必须创建数量巨大的类组合,从而使类的层次结构膨胀到不可思议的程序。

这时候就可以用 组合 来替换 继承,将不同“维度”的功能抽取到各自的类层次结构中。这部分内容在后续的策略模式中会讨论。

SOLID 原则

单一职责原则

Single Responsibility Principle

修改一个类的原因只能有一个。

这条原则的主要目的是减少复杂度。如果有下面的问题产生,就考虑是否违反了这个原则:

  • 在一个类中查找代码的速度非常慢,有时候要浏览整个类才能找到需要的内容。

  • 有一个改动就必须对类进行修改,这个修改会导致其它功能失效引发 BUG。

开闭原则

Open/closed Principle

对于扩展,类应该是“开放”的;对于修改,类应该是“封闭”的;

里氏替换原则

Liskov Substitution Principle

当你扩展一个类时,记住你应该要能在不修改客户端代码的情况下将子类的对象作为父类对象进行传递。

接口隔离原则

Interface Segregation Principle

客户端不应被强迫依赖于其不使用的方法。

依赖倒置原则

Depencency Inversion Principle

高层次的类不应该依赖于低层次的类。两者都应该依赖于抽象接口。抽象接口不应依赖于具体实现。具体实现应该依赖于抽象接口。

CATALOG
  1. 1. 设计原则
    1. 1.1. 封装变化的内容
    2. 1.2. 面向接口进行开发,而不是面向实现
    3. 1.3. 组合优于继承
  2. 2. SOLID 原则
    1. 2.1. 单一职责原则
    2. 2.2. 开闭原则
    3. 2.3. 里氏替换原则
    4. 2.4. 接口隔离原则
    5. 2.5. 依赖倒置原则