这篇文章介绍下著名的设计原则:SOLID

单一职责原则SIP (Single Responsibility Principle)

每个软件模块都应该有且只有一个被改变的理由。一个类只能承担一件事。

开闭原则OCP (Open-closed Principle)

开闭原则指软件实体(类、模块等)应当对扩展开放,对修改闭合。允许扩展行为和无需修改原来的代码。

没有人能够在一开始就识别出所有扩展点,也不可能在所有地方都预留出扩展点,这么做的成本是不可接受的。因此一定是由需求变化驱动。如果你有领域专家的支持,他可以帮你识别出变化点。

实现开闭原则的关键是抽象。在Bertrand Meyer提出开闭原则的年代(上世纪80年代),在类库中增加属性或方法,都不可避免地要修改依赖此类库的代码。这显然导致软件很难维护,因此他强调的是要允许通过继承来扩展类。随着技术发展,我们有了更多的方法来实现开闭原则,包括接口、抽象类、策略模式等。

我们也许永远都无法完全做到开闭原则,但不妨碍它是设计的终极目标。

里氏替换原则LSP (Liskov Substitution Principle)

程序中的对象应该可以被其子类实例替换掉,而不影响程序的正确性。

接口隔离原则ISP (Interface Segregation Principle)

使用多个特定细分的接口比单一的总接口要好,不能强迫用户去依赖他们用不到的接口。

依赖倒置原则DIP (Dependence Inversion Principle)

程序要以来于抽象接口,而不是具体实现:

  1. 高层模块不应该依赖于底层模块,两者都应该依赖于抽象
  2. 抽象不应该依赖于具体实现,具体实现应该依赖抽象

依赖倒置原则是区分过程式编程和面向对象编程的分水岭。过程式编程的依赖没有倒置,A Simple DIP Example | Agile Principles, Patterns, and Practices in C#这篇文章以开关和灯的例子很好地说明了这一点。

上图的关系中,当Button直接调用灯的开和关时,Button就依赖于灯了。其代码完全是过程式编程:

public class Button {   
    private Lamp lamp;   
    public void Poll()   {
        if (/*some condition*/)
           lamp.TurnOn();   
    } 
}

如果Button还想控制电视机,微波炉怎么办?应对这种变化的办法就是抽象,抽象出Role interface ButtonServer:

不管是电灯,还是电视机,只要实现了ButtonServer,Button都可以控制。这是面向对象的编程方式。

参考

  1. https://zhuanlan.zhihu.com/p/44344256
  2. https://zhuanlan.zhihu.com/p/130954951