大话设计模式 | 3. SOLID 原则
《 大话设计模式 》是作者「程杰」通过趣味的场景设置,以诙谐的表达来解读和剖析「面向对象」编程思维和「设计模式」。书中的示例代码是以 .NET 的 C# 语言编写而成。
本文是我对《大话设计模式》的学习系列笔记的第三篇,对单一职责原则、开放-封闭原则、里氏替换原则、接口隔离原则和依赖倒转原则的学习。
由于这些原则没有具体的代码实现,并且这些原则也是被统称为SOLID原则,这里便做一个统一的学习和记录。
SOLID原则
SOLID原则 包含单一职责、开闭原则、里氏替换、接口隔离,和依赖反转原则,是由 罗伯特·C·马丁 在21世纪引入的指代了面向对象编程和面向对象设计的五个基本原则。通过这些原则的使用,可以帮助程序员开发出一个容易维护和扩展的系统。
SOLID原则具体指代:
S (SRP, Single-responsibility principle),单一职责原则,认为对象应该仅具有一种单一功能。
O (OCP, Open-closed Principle),开放-封闭原则,认为软件体应该是对于扩展开放的,但是对于修改是封闭的。
L ( LSP, Liskov substitution principle),里氏替换原则,认为程序中的对象应该是可以在不改变程序正确性的前提下被它的子类替换的。
I( ISP, Interface segregation principle),接口隔离原则,认为多个特定客户端接口要好于一个宽泛用途的接口。
D (DIP, Dependency inversion principle),依赖倒转原则,认为一个方法应该遵从「依赖于抽象而不是一个实例」的概念。依赖注入是该原则的一种实现方式。
1. 单一职责原则(Single-responsibility principle)
单一职责原则,认为对象应该仅具有一种单一功能,或者说,就一个类而言,应该仅有一个引起它变化的原因。
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力,这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。要去发现职责并将这些职责相互分离的方式是,如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责。
作者用了手机功能和俄罗斯方块的游戏设计作为例子来说明,在我理解起来不是很清晰。三级小野怪的描述还是较为易懂的,这里做一些摘抄。
问题:某个类T负责两个不同的职责P1和P2,当职责P1需求发生变化而需要修改类T时,可能会导致职责P2的功能发生故障。
解决方案:根据单一职责原则,对职责P1和职责P2,分别建立两个类T1和类T2。
在实际应用中,可能大家都会自觉遵守这一原则,但是也存在「职责扩散」的问题,也就是由于某些原因,原来的职责P被分化成了粒度更细的职责P1和P2。
具体可以参考:设计模式六大原则(1):单一职责原则
2. 开放-封闭原则(Open-closed principle)
开放-封闭原则,认为软件体应该是对于扩展开放的,但是对于修改是封闭的。或者说软件实体(类、模块、函数等)应该可以扩展,但是不可修改。这里包含两个特征:
对于扩展是开放的(Open for extension)
对于修改是封闭的(Closed for modification)
任何系统在开发完成后,总是会根据需求的变化、升级等做一些变化,我们要通过怎样的设计来面对需求的改变,却可以保持相对的稳定呢?这就是开放-封闭原则要解决的问题。
开放-封闭原则需要在设计时,就考虑让某个类足够的好,写好了就不要去做修改了,如果新需求来了就增加一些类,原来的代码能不动就不动。但总是会存在一些变化,使得不可能不对这个类做修改(封闭),那么就要在设计时先考虑未来会做哪些变化(猜测),然后构造抽象来抽离那些变化。
实际上,这种是很难预测的,我们需要等到发生变化时立即采取行动。也就是说在最初编写代码时,可以假设不会发生变化,当变化发生时,我们就创建抽象来隔离以后会发生的同类变化。
比如之前的计算器,在一开始只有加法运算时,可以很快的去完成,但是当需要增加功能时,就需要去重构代码,增加一个抽象的运算类,通过面向对象的方法来隔离具体的算法、减少与客户端的耦合,以后有新功能引入时也能应对变化。
开放-封闭原则是面向对象设计的核心所在,遵循这个原则可以带来面向对技术所带来的可维护、可扩展、可复用和灵活性好的优势。开发人员应该仅对程序中呈现出频繁变化的那些部分做出抽象,然而,对于应用程序中的每个部分都刻意地进行抽象同样不是一个好主意,拒绝不成熟的抽象和抽象本身一样重要。
3. 里氏替换原则(Liskov substitution principle)
里氏替换原则是由 Barbara Liskov 提出,认为程序中的对象应该是可以在不改变程序正确性的前提下被它的子类替换的。或者说子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:
子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
子类中可以增加自己特有的方法。
有了里氏替换原则,才使得继承复用成为了可能,只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。比如,猫是继承动物类的,以动物身份拥有吃、喝等行为,当我们需要狗、牛时,只需继承动物,更改实例化的地方就可以了,不用修改程序的其他地方。
注:
我当前对该原则的理解还是模糊的
4. 接口隔离原则(Interface segregation principle)
接口隔离原则认为多个特定客户端接口要好于一个宽泛用途的接口。或者说客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
问题:类A 通过接口I 依赖类B,类C 通过接口I 依赖类D,如果接口I 对于类A 和类B 来说不是最小接口,则类B 和类D 必须要去实现他们不需要的方法。
解决:将有重复的接口I 拆分成独立的几个接口,类A 和类C 分别与他们需要的接口建立依赖关系。
如下图所示,在未使用接口隔离原则时,类A 依赖接口I 中的方法1、方法2、方法3;类C 依赖接口I中的方法1、方法4、方法5。对于类B 和类D 分别为对类A 和类D 依赖的实现,虽然他们都存在着用不到的方法(也就是图中红色字体标记的方法),但由于实现了接口I,所以也必须要实现这些用不到的方法。
接口隔离原则与单一职责原则的区别:
单一职责原则原注重的是职责,而接口隔离原则注重对接口依赖的隔离
单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建
5. 依赖倒转原则(Dependency inversion principle)
依赖倒转原则,认为一个方法应该遵从「依赖于抽象而不是一个实例」的概念。抽象不应该依赖细节,细节应该依赖于抽象。或者说,高层模块不应该依赖底层模块,两个都应该依赖抽象。
如下图中,高层对象A 依赖于底层对象B 的实现,依赖倒转就是把高层对象A 对底层对象的需求抽象为一个接口A,底层对象B 实现了接口A。
依赖倒转原则是基于:相对于细节的多边性,抽象的东西要稳定的多,其核心思想是「面向接口编程」。
在本书中,作者是以电脑的各个部件,如主板、CPU、内存、硬盘等针对接口设计为例加以说明,因为都是依赖于接口,所以内存出问题来,并不会造成其他的部件不可用,换一块内存就行了。
在设计模式六大原则(3):依赖倒置原则中,作者以妈妈讲故事(书、报纸等)为例来加以说明。将妈妈与具体的书的关联改变为妈妈与读这个抽象的接口进行关联。
在使用继承时需要遵循里氏替换原则。
版权声明: 本文为 InfoQ 作者【Puran】的原创文章。
原文链接:【http://xie.infoq.cn/article/53c6f7a700d94558db090efbf】。文章转载请联系作者。
评论