天天看点

软件设计的基本原则概述

软件的设计是一门大学问,在面向对象的设计中时常听到两个词:可维护性和复用性。本来不想写这样一篇基本是读书笔记性质的文章。但是想到去年对一位立志要成为公务员的仁兄的承诺,为了我们国土部门将来的公务人员有可以“教训”辛苦的 coder 的能力,决定将自己对于《设计模式》这本整整 1K 页的书作个浓缩整理,今天先不谈具体的设计模式,只把一些软件设计的基本原则作个总结。

要想两手同时抓住软件的可维护性和复用性,需要遵守以下几个原则。考虑到仁兄的英语非常的好,所以附上 E 文的具体写法,这又成为了将来可以进一步体现“技术加管理的橄榄球型人才”重要法宝。:)

1 .“开闭”原则( Open-Closed Principle )

2 .里氏代换原则( Liskov Substitution Principle )

3 .依赖倒转原则( Dependency Inversion Principle )

4 .接口隔离原则( Interface Segregation Principle )

5 .组合 / 聚合原则( Composition/Aggregation Reuse Principle )

6 .迪米特法则( Law of Demeter )

下面将对这 6 个基本法则作一些简单的总结和介绍。

“开闭 ” 原则( Open-Closed Principle )

“开闭”原则:一个软件实体应该对扩展开放,对修改关闭。一听,简直就是一句 P 话。因为,中文的修改的含义包含了这里扩展的概念。这里准确的,我们中国人能听懂的说法是:软件支持功能的上的扩展,但是要避免对原有代码的大幅度的修改 。

如何达到这样的要求呢?抽象化是关键。我们利用接口和抽象类来抽象出软件必须提供的行为特征,这样的抽象既是一种约定,同时也是对将来功能的预见,这样在将来就对系统的“修改关闭”了。

同时,我们可以从抽象出的接口实现新的类,以此来改变软件整体的行为,这样就达到了“对扩展开放”。

不知道有没有说清楚,如果没有看下面的例子:

我们有个家具厂,它必须能造桌子和椅子,那么它就必须有两条生产线,两个生产线都必须能完成“生产”这个动作,于是我们将两个生产线抽象成一个抽象的,具有“生产”行为的接口 ­ — “生 产线”,把桌子,椅子抽象成通用的“家具”。这样,哪天这个家具厂的老板发现造茶几也很赚钱,那么他从生产线这个抽象中具体出一个“茶几生产线”,从家具 这个抽象中具体出一个“茶几”就可以了。原来的生产不会受影响。这就是对扩展开放,对修改关闭。而这一切的关键就是有对于“产品”和“生产线”的抽象。

对于“开闭”原则,我们在工程上将它描述成“可变性封装原则”( Principle of Encapsulation of Variation )。从字面上,就是把系统的可变性封装起来。它意味着:

1 .对于一种可变性不应该散落在软件的各个角落,而应该把它封装成一个对象。

2 .一种可变性不应该和别的可变性混淆在一起。

这里可变性就可以理解为可扩展性。上面对于“产品”和“生产线”的不同可变性就应该分别抽象出这样两个接口,而不能将它们搞成一个叫“工厂”的接口,因为那样显然对于系统没有用。

里氏代换原则( Liskov Substitution Principle )

里 氏代换原则,是由我们上面说的“开闭原则”在从抽象到具体的过程中引出的。它表述为一个行为对父类可行的,对子类也一定可行。用一个通俗的例子,父类是 人,子类是中国人,人都有吃饭的功能,所以“请人吃饭”的行为对人适用,那么多中国人也适用;但是中国人有自己的特别行为,比如“说中文”。这样一个“讲 解《荷塘月色》”的行为只能是适合中国人这个子类,不能说它对任何人类都适用。比如基里巴斯那里出来的,估计人家不知道有中国这个国家。

依赖倒转原则( Dependency Inversion Principle )

它就一句话“依赖抽象,不要依赖具体”。换成一句时髦的话叫“对接口编成”。为什么能对接口编程?因为,接口就是具体的抽象,具体能干的,它都能干。 GOF95 那本书说,我们在定义引用变量时用接口就使这个意思。用上面的那个家具生产的例子表述就是

生产线 我的生产线 = new 桌子生产线();

而不是:

桌子生产线 我的生产线 = new 桌子生产线();

接口隔离原则( Interface Segregation Principle )

接 口是对不同类型对象的抽象,同时也是一种角色的划分。这个原则要求我们定义多个不同的专门接口,而不要依赖一个大而全的接口。比如我们在说“可变性封装原 则”的时候,我们说到,不能出现不同的可变性互相干扰的情况。就是对接口隔离原则的解释。在家具生产的例子里。定义“生产线”,和“产品 ” 这两个接口就是角色的清晰划分,如果将它们混合成一个“家具生产”的接口,这样的接口在角色定义上就失去了意义。

组合 / 聚合原则( Composition/Aggregation Reuse Principle )

组合 / 聚合原则也叫做组合 / 聚合复用原则,但是我觉得应该准确的称为组合 / 聚合优先原则,它优先于哪个?优先于继承。在《 Thinking in Java 》 中也有对这个原则的描述。难道这个原则是叫我们不要用继承?那我们从抽象类到具体类的如何实现呢?这不是和第一个“开闭原则”要求抽象矛盾么。其实这里的 优于继承是说,我们在想获得一个对象的功能的时候,不要随便通过继承,定义一个它的子类来获得它的方法和成员,而应该优先考虑通过拥有它的一个实例的方式 来实现。就是 new 一个对象来获得它的方法,优先于 extends 来获得它的方法。其实这个原则不但和“开闭”原则不矛盾,它还准确的告诉了我们,对于接口和抽象类,它们完成的仅仅是对一些通用行为的约定或一般的功能性实现而已。

这里组合和聚合的区别不作具体的描述,在对象关系描述里,这两个关系仅仅是根据整体和部分的紧密程度以及部分离开整体能否存活来区别的。举个小例子,你的四肢和你这个整体之间应该定义成组合,而你和咱们的篮球队之间就使聚合关系了。在 JAVA 里都是整体有一个部分的成员变量来表示的。

迪米特法则( Law of Demeter )

迪 米特法则规定的是模块与模块之间的通信量。狭义上说,迪米特法则要求两个类如果不应当直接发生关系,就应该通过一个第三方类来实现,这样一个模块和外界的 接触越少就越有利。但是这里不是让你,只要想和一个模块交流都搞个第三方类出来,而是通过一个第三方类,尽量解决它和其它大多数类的通信问题。

广义上说这个法则是对信息间的信息流量,流向以及信息的影响的控制。我们知道一个模块它与外界通信越少,那它的耦合度就越低,那么对于它的改进,操作对整体系统的影响就越小。所以迪米特法则也可以广义的看成是一个降低模块耦合度的表述。

到此为止,软件设计的 6 个基本原则就说完了,第一次写这样的文章不知道说明白没有。我力求不加入编程语言的例子, 用最简单的事实来表述。以后几天,我会陆续就根据这几个原则,对于经典的 23 条设计模式分别说明。希望对有志理解设计模式的同学,同事,老友有所帮助。当然,自己再看到那位仁兄的时候,也不会在良心上太过意不去。:)