学习设计模式第二 - 基础设计原则
本文大部分内容整理自《大话设计模式》
设计模式都建立在设计原则之上,本文介绍了几种最重要的设计原则。通过了解这些原则,我们才能更好的知道为什么要引入这么多设计模式。
-
单一职责原则(Single Responsibility Principle , SRP)
简述:
就一个类而言,应该仅有一个引起它变化的原因。
解释:
如果一个类承担的责任过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,涉及会遭受到意想不到的破坏。软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责,这时就该考虑类的职责分离。
内聚:
这个术语用于度量一个类或模块紧密地达到单一目的或责任。
当一个模块或一个类被设计成只支持一组相关的功能时,我们说它具有高内聚;反之就是低内聚。遵守单一职责原则就可以设计出内聚度很高的类,这样的类也具有更好的可维护性。
-
开放-封闭原则(The Open/Closed Principle, OCP)
简述:
开放-封闭原则指软件实体(类、模块、函数等)应该可以扩展,但是不可以修改。
解释:
这条原则有两大特征:一,对于扩展是开放的(Open for extension);二,对于更改是封闭的(Closed for modification)。坚持这条原则,我们的设计就能面对需求的改变却可以保持相对稳定,从而使得系统可以在第一个版本后的基础上不断发展而不至于推到重来。无论模块是多么的"封闭",都会存在一些无法对之封闭的变化。既然不可能完全封闭,设计人员必须对于他设计的模块应该对哪些变化封闭做出选择。他必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化。这真是后文介绍的"封装变化原则"。
总结:
面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码。开放封闭原则是面向对象设计的核心所在。遵循这个原则可以带来面向对象技术所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中呈现出频繁变化的那些部分做出抽象,然而,对于应用程序中的每个部分都刻意的进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。
-
依赖倒转原则(The Dependency Inversion Principle, DIP)
简述:
抽象不应该依赖细节,细节应该依赖于抽象。相对针对接口编程原则,这条原则更强调了抽象的重要性。
遵守依赖倒转原则应该掌握两条:
-
高层模块不应该依赖底层模块。两个都应该依赖抽象。
-
抽象不应该依赖细节。细节应该依赖抽象。
解释:
依赖倒转可以说是面向对象设计的标志,用哪种语言来编写程序不重要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依赖关系都是终止于抽象类或者接口,那就是面向对象的设计,反之就是过程化的设计。
如果遵循这条原则:
-
变量不可以持有具体类的引用。使用new会带来具体类的引用,可以适时使用工厂避开。
-
不要让类派生自具体类。如果派生自具体类,就会对具体产生依赖。应该派生自抽象类或实现接口。
-
不要覆盖基类中已实现的方法。如果覆盖基类已实现的方法,那么你的基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有的子类共享。
即使有些时候需要违反这些原则,也要仔细考虑是否有足够的理由这样做。
-
-
针对接口编程
简述:
针对接口编程,而不是针对实现编程。
解释:
这条原则中接口是一个泛指,换一种说法即针对抽象编程。这个抽象既可以是狭义上的接口(类型),也可以是抽象类。
-
多组合少继承 也称合成/聚合复用原则(CARP)原则
简述:
尽量使用合成/聚合,尽量不要使用类继承。
更简单的说多用组合,少用继承。
实现效果:
使用组合建立系统具有很大的弹性,不仅可将算法族封装成类,更可以"在运行时动态地改变行为",只要组合的行为对象符合正确的接口标准即可。
对象的继承关系是在编译时就定义好了,所以无法在运行时改变从父类继承的实现。子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当你需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。而优先使用对象的合成/聚合将有助于你保持每个类被封装,并被集中在单个任务上。这样类和类的继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。
组合用在"许多"设计模式中。
-
松耦合
简述:
为了交互对象之间的松耦合设计而努力。
实现效果:
松耦合的设计将对象之间的互相依赖降到了最低,从而使我们可以建立有弹性的OO系统,更好的应对变化。
-
里氏代换原则(The Liskov Substitution Principle, LSP)
简述:
子类型必须能够替换掉它们的父类型。
解释:
一个软件实体如果使用一个父类的话,那么一定适用于其子类,而且它觉察不出父类对象和子类对象的区别。也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化。只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。而正是由于子类型的可替换性才使得使用父类类型的模块在无需修改的情况下就可以扩展。
-
封装变化
简述:
把会变化的部分取出并"封装"起来,好让其他部分不会受到影响。
原则解释:
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
另一种解释:
把会变化的部分取出并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分。
实现效果:
代码变化引起的不经意后果变少,系统变得更有弹性。
这条设计原则是几乎每个设计的精神所在。所有的模式都有让"系统中的某部分改变不会影响其他部分"的方法。实现这条原则,我们需要在变化发生时立即采取行动。在我们最初编写代码时,假设变化不会发生。当变化发生时,我们就创建抽象来隔离以后发生的同类变化。我们希望的是在开发工作展开不久就知道可能发生的变化。查明可能发生的变化所等待的时间越长,要创建正确的抽象就越困难。
-
接口分离原则(Interface Segregation Principle, ISP)
简述:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
原则解释:
使用多个专门的接口比使用单一的总接口要好。
一个类对另外一个类的依赖性应当是建立在最小的接口上的。
一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。
"不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构。"这个说得很明白了,再通俗点说,不要强迫客户使用它们不用的方法,如果强迫用户使用它们不使用的方法,那么这些客户就会面临由于这些不使用的方法的改变所带来的改变。
-
"最少知识"原则(Least Knowledge)
简述:最少知识原则告诉我们要减少对象之间的交互,只留下几个"密友"。
原则解释:
这个原则希望我们在设计中,不要让太多的类耦合在一起,免得修改系统中一部分,会影响到其他部分。如果许多类之间相互依赖,那么这个系统就会变成一个易碎的系统,需要花许多成本维护它,而以因为太复杂而不容易被他人了解。
这个原则的一些方针:
对任何对象而言,在对象的方法内,我们只应该调用属于以下范围的方法:
-
对象本身的方法
-
作为参数传入的对象的方法
-
在方法内创建或实例化的任何对象的方法
-
对象中引用的组件(使用组合关系包含的对象)的方法
而不应该调用调用一个方法返回的对象的方法。我们应该让当前对象直接发出请求,而不是通过另一个对象。中介者模式就是遵守这个法则的一个极佳例子。
这个法则还有两个别名:得墨忒尔法则(Law of Demeter)和迪米特法则。
这个法则的缺点在于有时候为了让两个组件可以沟通,需要增加更多的包装类,这样可能会导致复杂度和开发时间增加,并会降低程序运行速度。
-
-
好莱坞原则
简述:别调用我们,我们会调用你。
原则解释:
好莱坞原则下,高层组件会决定什么时候和怎样使用低层组件。换句话说,高层组件对待低层组件的方式是"别调用我们,我们会调用你"。
尽可能做到低层组件不调用高层组件中的方法。