设计原则 - 适应变化

发现 JavaScript 很好的一点就是不太受设计模式的荼毒,也没有什么多重继承,多态的困扰,更没有强制使用接口或者虚函数来实现抽象类,虽然也有办法来模拟这些功能,但是那么做的人估计不多。估计这和 JavaScript 不是严格面向对象,而偏向函数式有关,估计也是 JavaScript 这么流行的部分原因吧。

很多时候感觉设计模式有点过了,特别是 Java 里,非常啰嗦。Web app 的开发迭代太快,代码的生存周期也更短,所以不太讲究设计模式。不过我认为很多通用的设计原则还是要的,以处理现实里不停的需求变化。这些原则其实都是为了适应软件开发的需求改变,以使软件保持相对的稳定。

这里讲的 模块 可以是面向对象语言的类 class;也可以是 JavaScript 的模块 module,对象 Object,甚至函数 function;也可以是更大层面的实体 entity。

很多人都嘲笑软件开发不属于工程学,因为软件动不动就崩溃蓝屏,大家也见怪不怪,对质量根本就不像其他工程学那样严格。试想造座桥可以动不动就垮塌吗?但是他们不知道的是,建桥的工程师也不会在大桥将要完工时突然产品经理来说我们把 4 车道改为 8 车道吧,然后再加高 15 米就可以过更多的车和更大的船了,应该很简单啊,不就是多些钢筋水泥而已嘛。

软件编程的总的原则:低耦合,高内聚 (High Cohesion & Low/Loose coupling)。这也是 Unix 系统的设计原则。

核心目的是为编写可维护,可扩展,可复用,灵活的代码

一、SOLID

以下最初五则首字母连在一起为 SOLID (SRP, OCP, LSP, ISP, DIP)。 SOLID 加上 LoD 和 CARP,是传统的面向对象设计(主要是设计类 classs)七大原则。

1. 单一职责原则 SRP

Single Responsibility Principle

一个模块或者函数应该仅有一个引起它变化的原因。 防止耦合,拥抱变化。 这也是软件设计最需要做的,发现职责,合理分离职责。一个判断依据就是如果你能想到多余一个动机去改变一个模块或函数,那么这个模块或函数就具有多余一个的职责。

2. 开放-封闭原则 OCP *

Open Closed Principal

软件的模块应该对于扩展是开放的(open for extension);对于修改是封闭的(closed for modification)。 面对新功能,最好是增加新模块,而不是更改现有的代码。 抽象出频繁变化的部分。但是要注意不成熟的抽象,和过早优化一样,都是邪恶的。

最重要,最抽象的原则。Reusable Software 是基于此原则开发的,其他的原则也是对它的实现提供了路径。

3. 里氏代换原则 LSP

Liskov Substitution Principle

子类(subtype)应能替换掉它们的基类 (base type). 里氏代换原则是继承复用的基石。所有支持继承复用的语言都满足里氏代换原则

4. 接口隔离原则 ISP

意思是把功能实现在接口中,而不是类中,使用多个专门的接口比使用单一的总接口要好。 准确而恰当地划分角色以及角色对应的的接口,是面向对象设计的一个的组成部 分。将没有关系的接口合并在一起,形成一个臃肿的大接口,是对角色和接口的 污染。

5. 依赖倒置原则 DIP

Dependency inversion principle

程序应该依赖抽象,而不是互相依赖。 高层模块不应该依赖底层模块,两者都应该依赖其抽象.
抽象不应该依赖细节,细节应该依赖抽象。
要针对抽象编程,不要针对实现编程。Program to an interface, not an implementation
倒转依赖强调一个系统内的实体之间关系的灵活性。基本上,如果设计师希望遵守开闭原则,那么依赖倒转原则便是达到要求的途径。
里氏代换原则是是依赖倒转原则的基础。
依赖倒转原则是面向对象设计的核心原则,设计模式的研究和应用是以依赖倒转原则为指导原则的。


6. 迪米特法则 LoD

Law of Demeter

最小知识原则 (LKP Least Knowledge Principle)。尽量降低成员的访问权限。也是强调松耦合。耦合越弱,越有利于复用;隐藏内部信息正好达到这点。

如果两个模块不必彼此直接通信,那么这两个模块就不应当发生直接的相互作用。如果其中一个需要调用另外一个的方法的话,可以通过第三者转发这个调用。

迪米特法则的初衷是降低模块之间的耦合。过分的使用迪米特原则,会产生大量这样的中介和传递模块,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。

7. 合成/聚合复用原则 CARP

Composition Aggregation Reuse Principle

尽量使用合成、聚合,少用继承。就是模块之间最好是组合在一起,组合优先于继承。 使用参数传递,或者在组合聚合关系中,尽量引用层次高的模块。


二、包/组件设计原则

Robert C. Martin 在《敏捷软件开发:原则、模式与实践》(Agile Software Development: Principles, Patterns, and Practices)一书中提出了一些用于设计包或组件的原则。

一共六个,前面三个关注组件的内聚性(Cohesion),用于指导如何组包;后面三个关注组件的耦合性(Coupling),用来确定组件之间的相互关系。简单来说,组件(或包)的设计也要做到“高内聚,低耦合”。不过有些原则描述的是一种理想状况,在实际使用时更多的是尽量趋向于这些原则,通常很难做到百分之百满足。

1.重用发布等价原则 REP

Release Reuse Equivalency Principle

重用的粒度就是发布的粒度。

2. 共同封闭原则 CCP

Common Closure Principle

包中的所有模块对于同一种性质的变化应该是共同封闭的。一个变化若对一个封闭的包产生影响,则将对该包中的所有类产生影响,而对于其他包则不造成任何影响。面向对象设计的原则之一。

对于开放-封闭原则 OCP,完全封闭是不现实的,所以在设计时,只能尽量地保持对大多数可预见的修改关闭。这里的 CCP 延伸了 OCP 的封闭概念,当因为某个原因需要修改时,把需要修改的范围限制在一个最小范围内的包里。

3.共同重用原则(CRP)

Common Reuse Principle

一个包中的所有类应该是共同重用的。

如果重用了包中的一个类,那么就要重用包中的所有类。相互之间没有紧密联系的类不应该在同一个包中。

CRP与REP一样,都是从方便用户重用的角度去设计包,重用者是他们的受益者,CCP则让系统的维护者受益。CCP让包尽可能大(CCP原则加入功能相关的类),CRP则让包尽可能小(CRP原则剔除不使用的类)。它们的出发点不一样,但不相互冲突。

CRP保证了包的内部具有很高的聚合性。

4.无环依赖原则 ADP

Acyclic Dependencies Principle

在包的依赖关系图中不允许存在环(循环依赖)。

5.稳定依赖原则 SDP

Stable Dependencies Principle

朝着稳定的方向进行依赖。一个包只应该依赖那些比自己更稳定的包。 应该把封装系统高层设计的软件(比如抽象类)放进稳定的包中, 不稳定的包中应该只包含那些很可能会改变的软件(比如具体类)。

6. 稳定抽象原则 SAP

Stable Abstractions Principle

稳定的包应该是抽象的包。 包的抽象程度应该和其稳定程度一致。最稳定的,不容易改变的包应该是最抽象的包,处于下层。不稳定的,容易改变的包应该是具体的包,处于上层。包的抽象程度跟它的稳定性成正比。


下面四个是关于包,组件扩展的原则。

1.缺省抽象原则 DAP

Default Abstraction Principle

在接口和实现接口的类之间引入一个抽象类,这个类实现了接口的大部分操作.

2.接口设计原则 IDP

Interface Design Principle

规划一个接口而不是实现一个接口。

3.黑盒原则 BBP

Black Box Principle

多用类的聚合,少用类的继承。

4.不要构造具体的超类原则 DCSP

Don't Concrete Supperclass Principle

避免维护具体的超类。


三、其他一些实践原则


1. Keep It Simple, Stupid (KISS)

KISS原则在设计上可能最被推崇的。系统界面简洁,傻瓜化、功能实用、操作方便。

2. You Ain’t Gonna Need It (YAGNI)

这个原则简而言之为——只考虑和设计必须的功能,避免过度设计。只实现目前需要的功能,在以后您需要更多功能时,可以再进行添加。

3. Don't repeat yourself (DRY)

不要重复编写代码。但我认为也不要过早优化。如果仅仅重复一次还是可以接受的,因为把那部分代码抽离出来也会占用时间,但是如果发现第三次编写同样地代码,就可以考虑把它们抽离出来为单独的模块或者函数。这也叫 Rule of Three。

4. Convention over Configuration(CoC)

尽量让惯性来减少配置提高开发效率,最终实现零配置。

5. Command Query Separation(CQS)

尽量在接口定义的时做到命令和查询分离。

6. Separation of Concerns(SoC)

尽量将复杂的问题分离成多个简单的问题然后逐个解决。

7. Hollywood Principle(HP)

好莱坞的制片,导演等人一般都很忙,经常会对那些来试完戏的业余演员讲:Don't call me, I'll call you.“(不要联系我,我会联系你)。这则其实和“控制反转”或者“依赖注入”类似,我们不需要在代码中主动的创建对象,而是由容器帮我们来创建并管理这些对象。

8. Design by Contract (DbC)

契约式设计。
DbC的核心思想是对软件系统中的元素之间相互合作以及“责任”与“义务”的比喻。这种比喻从商业活动中“客户”与“供应商”达成“契约”而得来。

酷壳的这篇文章也很不错

最后,更多的可见维基百科的一份软件开发哲学清单 List of software development philosophies