上篇总结了经典的23种 设计模式,详细的解读后期会陆续的详细揭开。使用设计模式的根本原因就是为了增强代码的复用性和可维护性。而面向对象是实现代码复用的有效途径,所以这里有必要了解一下OO的基本思想和原则。
面向对象设计的原则(OOD&OOP)主要分为两大类,一类是面向类的,另一类是面向包的。设计模式基本都是围绕面向类的几个原则的实践,而面向包的几个原则主要体现在架构模式中。
S.O.L.I.D
Bob大叔(Robert C. Martin)的大名如雷贯耳,相信大部分在进击中的开发者都有阅读过经典著作《敏捷软件开发》的经历,受益匪浅。
在面向对象的程序的设计和开发过程中时,有5个原则非常重要,此外还有一个法则我们应该尽量去遵守它。
1、单一职责原则(SRP,The Single Responsibility Principle)
2、开放封闭原则(OCP,The Open Closed Principle)
3、里氏替换原则(LSP,The Liskov Substitution Principle)
4、依赖倒置原则(DIP,The Dependency Inversion Principle)
5、接口分离原则(ISP,The Interface Segregation Principle)
6、迪米特法则(LoD,The Law of Demeter、LKP)
SRP
A class should have only one reason to change.
一个类应该仅有一个引起它变化的原因。
SRP是所有原则中最简单的一个,概念简洁易懂。但却也是最难正确运用,或者说是最容易过度使用的原则。
一方面,因为SRP把职责定义成了变化的原因,如果我们能够找到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责。比如开发者经常会习惯性的把职责归类式的结合在一起,而正确的做法是发现职责并把这些职责相互分离。
另一方面,如果程序的变化方式总是导致N个职责(N>1)同时变化,那么就没必要分离它们了。在这里有一个推论:仅当变化发生时,变化的轴线才具有实际的意义。在实际的设计和开发过程中,一定要把握好分离“单个”职责的度,所谓过犹不及,过度的分离只能适得其反,违背了面向对象的封装思想。
OCP
Software entities(classes,modules,functions,etc.) should be open for extension, but closed for modification.
软件实体(如类、模块、函数等)应该对扩展开放,对修改关闭。
OCP是整个面向对象设计的核心。它代表了灵活性、可重用性和可维护性。
乍一看,两个特征似乎有所矛盾,扩展模块的功能通常是以修改模块的源代码作为手段。但是,我们有“抽象”!通过创建稳定的但又能描述一组任意个可能行为的抽象基类,并将具体的行为实现为派生类,这种做法能很好的解决开放扩展的问题。主要是针对软件中频繁变化的那部分作出合理的抽象。
当然,创建和使用抽象会增加软件复杂性和团队的精力,所以肆意的进行抽象显然并不是一个好设计,简直是折磨人。除非你有强大的智商优越感,但是面对广大平凡质朴的程序员,还请手下留情。
拒绝不成熟的抽象和抽象本身一样重要。
LSP
Derived types must be completely substitutable for their base types.
子类型必须能够完全的替换掉它们的基类型。
LSP是使OCP成为可能的主要原则之一,也是继承层次特征的主要设计原则。
一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,在软件中,把父类都替换成它的子类,程序行为没有或者不发生变化。也就是说,只有当子类可以替换掉父类,软件的功能不受到影响时,父类才能真正的被复用,而子类也能够真正的在父类的基础上增加新的行为。
子类型的可替换性使得使用基类型表示的模块在无需修改的情况下就可以扩展。可替换性可以通过显示或者隐式的契约来定义。
DIP
A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
高层模块不应该依赖于低层模块,两者都应该依赖于抽象
B. Abstractions should not depend on details. Details should depend on abstractions.
抽象不应该依赖于细节,细节应该依赖于抽象
DIP是面向对象设计的标志。
依赖倒置原则是实现许多面向对象技术所宣称的好处的基本低层机制,他的正确应用对于创建可重用的框架来说是必须的。同时它对于构建在变化面前富有弹性的代码也是非常重要的,由于抽象和细节彼此隔离,所以代码也非常容易维护。
针对接口编程,而不要对实现编程。倒置接口的所有权。
LSP和DIP乍一看还有点相似,其实,他们还是有区别的:
两个原则所站的角度不同。LSP是站在模式对象的角度,而DIP则是站在客户端程序的角度;模式对象一方将“相对多变的”子类视同它的接口(或父类),而客户程序依赖的内容是“相对稳定”的接口。
ISP
Clients should not be forced to depend upon interfaces that they don't use.
不应该强迫客户程序依赖并未使用的接口。
ISP有效的降低了程序间的耦合性,提高了程序集的内聚性。避免了接口污染。
客户程序应该仅仅依赖于他们实际调用的方法。分离客户就是分离接口。通常我们可以使用委托或者使用多重继承来分离接口。
接口隔离原则不是单纯的多余或是不多余,从设计的角度来衡量,它要依据业务领域的需要判断倒是是否真的“多余”,确保在每个领域背景下贯彻不去依赖用不到的方法。通过把胖类的接口分解为多个特定于客户程序的接口,可以实现这个目标。有效的解除了客户程序和它们没有调用到的方法间的依赖关系。
客户程序看到的应该是多个具有内聚接口的抽象基类。
LoD(LKP)
A. Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
B. Each unit should only talk to its friends; don't talk to strangers.
C. Only talk to your immediate friends.
迪米特法则又称为最少知识原则。主要有三层意思,就是让调用者对于目标对象的知识最少,而且只和你的直接朋友对话,千万不要和陌生人说话。
该法则因在《程序员的修炼之道》一书中提及而声名鹊起广为人知。其初衷在于降低类之间的耦合,但是由于通过友元类来建立一个类和其他类的联系,或是减少对其他类的依赖,这导致了在一定程度上增加了系统的复杂度。
小结
这些原则是数十年软件工程经验来之不易的结果。是众多开发人员和研究人员思想和著作的结晶。我们应该牢记面向对象设计的原则,理解OO的设计思路。因为它们有助于开发人员消除拙劣的设计和代码臭味,并能有效的帮助开发人员构建出最适合于当前特性集的设计。
Bob大叔建议:在日常工作中,只有当出现“臭味”时我们才会去使用它,如果只是因为它是一个原则就无条件的遵循它那是错误的,过分的遵循这些原则会导致不必要的复杂性和设计臭味。
个人认为,一切均应切合实际,要考虑到团队的承受能力,项目的可控范围等等因素。
不要把它当作可以在系统中随意喷洒的香水!
结尾:上面主要记述了面向类(对象)的几个设计模式原则,后续会论述面向包设计的架构模式原则。