怎样意味着适当的设计?一个被适当地设计了的系统是容易被理解的、容易改变的和容易重用的。它表现为没有特别的开发困难,是简单的、扼要的和经济的。使用它是一件愉快的事情。相反地,一个糟糕的设计散发出像腐烂的肉般的臭味。
臭哄哄的设计:
当一个程序员正忙于一个糟糕的设计时,你能够看到他查看代码时眼睛和鼻子的样子。 如果他或她的面部表情仿佛像刚刚打开了一个装有已经死了 12天的尸体的裹尸袋一般,毫 无疑问,这个设计也许是相当臭的。一个糟糕的设计的臭味有许多不同的成分:
1)僵化性(Rigidity):系统难以被改变,因为每当你进行一个改动时,你不得不做其他没完没了的相关改动。
2)脆弱性(Fragility):针对系统的某一部分的改变,将导致许多系统中的其他完全不相关的部分的出现问题。
3)牢固性(immobility):很难将系统拆分成可被其他系统重用的组件。
4)粘滞性(Viscosity):这个开发环境的各个部分被像用胶袋纸和牙膏般地糊弄在一起的,进行编辑、编译和测试时折腾起来是没完没了的。
5)不必要的复杂性(Needless Complexity):存在着许多的精巧的代码结构,但是它们只将将来某一天非常有用,而对于现在是没有必要的。
6)不必要的重复(Needless repetition):代码看起来是被叫”cut”和”paste”的两个程序员编写的,系统存在着重复的代码。
7)晦涩性(Opacity):由于创作者的表达不够明晰,去阐明创作者的意图时表现得相当困难。
依存关系管理:
许多的臭味是无效管理的依存关系的结果。无效管理的依存关系导致代码的视图像一团 乱麻,这种纠缠在一起的视图是术语“意大利式细面条般代码”的来源。面向对象语言提供了有助于管理依存关系的工具,创建接口(interface)能够打破和转化 一些依存关系的用法。
多态性(Polymorphism)允许模块调用那些没有包含在内的的其他模块 的函数。实际上,一个 OOPL 给了我们许多的方法去定型依存关系。
良好的设计原则:
1)单一职责原则(SRP)
一个类应当只有一个改变的原因。
2)开放-封闭原则(OCP)
软件实体(类、模块、函数等)应当为扩展而开放,又为修改而封闭。
这个原则有一个相当详细的定义,但是一个简单的意思是:你应当能够改变一个模块的周边环境而无须改变模块本身。
3)Liskov 替换原则(LSP)
子类型(subtypes)必须是为它们的基类型(base types)可替代的。
你曾经在 if 语句的子句中看过一些有 instanceof 表达式的代码吗?虽然像这种表达式是合法的,也是较少的,其实通常它们是违反了LSP原则的结果,同时它们也违反了OCP 原则。这个 LSP 原则是指基类(base classs)的用户为了使用它们的派生类,应当无须做特别 的处理。特别是,它们应当无须使用 instanceof 或向下转型(Downcast)操作。实际上,它 们根本无须知道有关派生类的事情,甚至无须知道它们是否存在。
Liskov 替换原则(LSP)
子类型(subtypes)必须是为它们的基类型(base types)可替代的。
你曾经在 if 语句的子句中看过一些有 instanceof 表达式的代码吗?虽然像这种表达式是 合法的,也是较少的,其实通常它们是违反了 LSP 原则的结果,同时它们也违反了 OCP 原则。这个 LSP 原则是指基类(base classs)的用户为了使用它们的派生类,应当无须做特别 的处理。特别是,它们应当无须使用 instanceof 或向下转型(Downcast)操作。实际上,它 们根本无须知道有关派生类的事情,甚至无须知道它们是否存在。
4)依存关系倒置原则(DIP)
A.高层模块应当不依赖低层模块,它们应当依赖于抽象。
B.抽象应当不依赖于细节,细节应当依赖于抽象。 更好的描述是:不要依赖那些容易变化的具体类。如果你要继承一个类,从一个抽象
类继承吧。如果你要持有一个类的引用,从一个抽象的类引用吧。如果你要调用一个函数, 从一个抽象的函数调用吧。
一般来说,对抽象类和接口的改变远不及由它们派生出来的具体的类的改变更频繁。因此,我们宁可依赖抽象(指抽象类和接口)而不是具体的实现。遵循这个原则将减少一个变化对系统的影响。
5)接口隔离原则(ISP)
客户不应当依赖那些它们根本不用的方法。
你是否曾经看到过一个肥类(fat class),一个肥类是一个有着成堆的方法的类,而在我 们的系统中并不需要如此多的类,但是有时它们是不可避免 。 伴随着肥类的问题除了它们过大和令人讨厌外,它们的用户极少地使用它们的方法.
小结
五个简单的原则是:
1、 SRP--一个类应当只有一个发生变化的原因。
2、 OCP――应当能够改变一个类的环境,而无须改变类本身。
3、 LSP――避免造成派生类的方法非法或退化,一个基类的用户应当不需要知道这个派生类。
4、 DIP――用依赖于接口和抽象类来替代依赖容易变化的具体类。
5、 ISP――给一个对象的每一个用户一个接口,这个接口仅有用户需要的方法。
什么时候我们应当应用这些原则呢?首先一个痛苦的提示是,试图让所有的系统时时刻刻遵循所有的原则那是不明智的。你将花费很长的时间试图去设想在所有不同的环境中应用 OCP 原则,或所有不同的发生改变的源头应用 SRP,你将要为应用 ISP 原则编写大量的小 接口,将为应用 DIP 原则而创建一堆没有价值的抽象。
应用这些原则的最好方式是与主动性式(proactively)相对的反应性式(reactively)的方法。当你第一次觉察到在代码里有一个结构性的问题存在的时候,或当你第一次意识到一个模块的改变被其他模块影响到了的时候,你应当看看是否能够运用这些原则去解决问题。
当然,如果你使用一个主动性式方法去应用这些原则,你也需要运用一个主动性式途径 去解决系统上的将造成痛苦的各种类型的压力。如果你对痛苦有了反应,你需要坚持不懈地 发现这个痛点(the sore spots)。