S.O.L.I.D 能帮助我们成为更优秀的开发人员。
S.O.L.I.D 是面向对象设计(OOD)的头五大基本原则的首字母缩写,由俗称「鲍勃大叔」的 Robert C. Martin 提出。
这些原则,结合在一起能够方便程序员开发易于维护和扩展的软件,也让开发人员轻松避免代码异味,易于重构代码,也是敏捷或自适应软件开发的一部分。
面向对象的五大原则:
单一职责原则SRP:Single Responsibility Principle
开放封闭原则OCP:Open-Close Principle
Liskov替换原则LSP:Liskov Substitution Principle
依赖倒置原则DIP:Dependency Invertion Principle
接口隔离原则ISP:Interface Separate Principle
在面向对象设计中,如何通过很小的设计改变就可以应对设计需求的变化,这是令设计者极为关注的问题。为此不少OO先驱提出了很多有关面向对象的设计原则用于指导OO的设计和开发。下面是几条与类设计相关的设计原则。
1. 单一职责(the Single Responsibility Principle SRP)
系统中的每一个对象都应该只有一个单独的职责,而所有对象所关注的就是自身职责的完成。
每一个职责都是一个设计的变因,需求变化的时候,需求变化反映为类职责的变化。当你系统里面的对象都只有一个变化的原因的时候,你就已经很好的遵循了SRP原则。
单一职责可以是类级别的单一,也可以是行为上的单一
2. 开闭原则(the Open Closed Principle OCP)
一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的。因此在进行面向对象设计时要尽量考虑接口封装机制、抽象机制和多态技术。该原则同样适合于非面向对象设计的方法,是软件工程 设计方法的重要原则之一。
我们以收音机的例子为例,讲述面向对象的开闭原则。我们收听节目时需要打开收音机电源,对准电台频率和进行音量调节。但是对于不同的收音机,实现这三个步骤的细节往往有所不同。比如自动收缩电台的收音机和按钮式收缩在操作细节上并不相同。因此,我们不太可能针对每种不同类型的收音机通过一个收音机类来实现(通过重载)这些不同的操作方式。但是我们可以定义一个收音机接口,提供开机、关机、增加频率、降低频率、增加音量、降低音量六个抽象方法。不同的收音机继承并实现这六个抽象方法。这样新增收音机类型不会影响其它原有的收音机类型,收音机类型扩展极为方便。此外,已存在的收音机类型在修改其操作方法时也不会影响到其它类型的收音机。
图1是一个应用OCP生成的收音机类图的例子:
图1 OCP应用(收音机)
3. 里氏替换原则 (the Liskov Substitution Principle LSP)
在项目中所有使用子类的地方都可用父类替换,但在调用方法的时候 ,即呈现面向对象编程的多态性。即替换原则,它是非常重要的原则, 这个原则是Liskov于1987年提出的
严格的定义:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都换成o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型。
通俗的定义:所有引用基类的地方必须能透明地使用其子类的对象。
更通俗的定义:子类可以扩展父类的功能,但不能改变父类原有的功能。
里氏替换原则包含以下4层含义:
1.子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
2.子类中可以增加自己特有的方法。
3.当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
Liskov的相关图示见图2:
图2 Liskov 原则
运用替换原则时,我们尽量把类B设计为抽象类或者接口,让C类继承类B(接口B)并实现操作A和操作B,运行时,类C实例替换B,这样我们即可进行新类的扩展(继承类B或接口B),同时无须对类A进行修改。
违反里氏代换原则意味着违反了开闭原则,反之未必。里氏代换原则是使代码符合开闭原则的一个重要保证。
4. 依赖倒置原则 (the Dependency Inversion Principle DIP)
在进行业务设计时,与特定业务有关的依赖关系应该尽量依赖接口和抽象类,而不是依赖于具体类。具体类只负责相关业务的实现,修改具体类不影响与特定业务有关的依赖关系。
在结构化设计中,我们可以看到底层的模块是对高层抽象模块的实现(高层抽象模块通过调用底层模块),这说明,抽象的模块要依赖具体实现相关的模块,底层模块的具体实现发生变动时将会严重影响高层抽象的模块,显然这是结构化方法的一个"硬伤"。
面向对象方法的依赖关系刚好相反,具体实现类依赖于抽象类和接口(见图-3)。
为此,我们在进行业务设计时,应尽量在接口或抽象类中定义业务方法的原型,并通过具体的实现类(子类)来实现该业务方法,业务方法内容的修改将不会影响到运行时业务方法的调用。
图3依赖原则图示
5. 接口隔离原则(the Interface Segregation Principle ISP)
采用多个与特定客户类有关的接口比采用一个通用的涵盖多个业务方法的接口要好。
ISP原则是另外一个支持诸如COM等组件化的使能技术。缺少ISP,组件、类的可用性和移植性将大打折扣。
这个原则的本质相当简单。如果你拥有一个针对多个客户的类,为每一个客户创建特定业务接口,然后使该客户类继承多个特定业务接口将比直接加载客户所需所有方法有效。
图4展示了一个拥有多个客户的类。它通过一个巨大的接口来服务所有的客户。只要针对客户A的方法发生改变,客户B和客户C就会受到影响。因此可能需要进行重新编译和发布。这是一种不幸的做法。
图4 带有集成接口的服务类
我们再看图-5中所展示的技术。每个特定客户所需的方法被置于特定的接口中,这些接口被Service类所继承并实现。
图5 使用接口分离的服务类设计
如果针对客户A的方法发生改变,客户B和客户C并不会受到任何影响,也不需要进行再次编译和重新发布。
以上四个原则是面向对象中常常用到的原则。此外,除上述四原则外,还有一些常用的经验诸如类结构层次以三到四层为宜、类的职责明确化(一个类对应一个具体职责)等可供我们在进行面向对象设计参考。但就上面的几个原则看来,我们看到这些类在几何分布上呈现树型拓扑的关系,这是一种良好、开放式的线性关系、具有较低的设计复杂度。一般说来,在软件设计中我们应当尽量避免出现带有闭包、循环的设计关系,它们反映的是较大的耦合度和设计复杂化。
依赖倒置原则:
“高层模块不依赖域低层模块,两者应该依赖于对象”
“抽象不应该依赖于细节,而细节应该依赖于抽象”
里氏替换原则:
如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都换成o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型
来源:https://www.cnblogs.com/zachary7/p/7975647.html