设计模式中的设计原则

先来抛一个问题,设计模式到底有几个原则?

翻了三本书《设计模式之禅》、《设计模式:可复用的面向对象软件元素》、《Head First 设计模式》,也看了不少博客和关于设计模式原则的文章。关于设计模式有几大原则,似乎没有严格的定论,有的说6大设计原则,有的说7大设计原则,《Head First》中更是提到了9个设计原则。 不管是多少个设计原则,最终都是希望程序达到 **“高内聚,低耦合” **,代码高度复用,具有可维护性的目的。所以多少个设计原则已经不重要了,重要的是达到怎样的目标!

设计原则

我觉得7大设计原则都有必要了解和尽量向其靠拢,但是程序设计肯定是不可能完全遵守这些设计原则,但是我们的设计可以让程序更好扩展和更容易维护。

1.开闭原则(Open Closed Principle,OCP)

开闭原则的原文定义是:Software entities should be open for extension,but closed for modification.(软件实体应该对扩展开放,对修改关闭。) 其意思是说一个软件程序应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。但是并不意味着不对代码做任何的修改,底层模块的变更,必然要有高层模块进行耦合,我们只能尽量预测变化,但是并不能保证所有的变更都不需要修改代码。 所以说,开闭原则具有理想主义的色彩,它是面向对象设计的终极目标。 开闭原则是面向对象设计中最基础的设计原则,它也被称为设计总则,它指导我们如何建立稳定灵活的系统。

2.单一职责原则(Single Responsiblity Principle ,SRP)

单一职责原则的原文定义是:There should never be more than one reason for a class to change.(应该有且仅有一个原因引起类的变更。) 在《设计模式之禅》中举了一个电话接口的例子。电话通话的时候有4个过程发生:拨号、通话、回应、挂机。那么写一个接口:

图-1

但是,这个IPhone 接口,并不是只有一个职责,它包含了两个职责:一个是协议管理,一个是数据传送。dial()和hangup()实现的是协议管理,分别负责拨号和挂机;chat()实现的是数据的传送。如果协议接通的变化肯定会引起接口或者实现类的变化;而数据传送的变化(电话不仅仅为了通话传送数据,还可以为上网传送数据)肯定也会引起接口或者实现类的变化,所以这里就有两个原因会引起接口或者类的变化。分析后,我们应该考虑拆分连个接口:

图-2

注意 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或者类设计的是否优良,但是“职责”或“变化原因”都是不可度量的,因项目而异,因环境而异。 因为“职责”没有一个量化的标准,一个类到底要负责哪些职责?这些职责该怎么细化?而项目后期职责发生扩展,可能一个职责要衍生出两个职责出来,该怎么拆分?在项目时间紧迫,接口或者类非常简单,考虑人工和事件成本时,是否还要坚持 单一职责原则?这些都要根据实际情况来考量。

3.里氏替换原则(Liskov Substitution Principle,LSP)

为什么这个原则的名字这么奇怪呢? 因为这个原则是由麻省理工学院的一位叫Barbara Liskov的女士提出来的,所以就叫里氏替换原则了。国外用发现或者创造定理、原则等是很正常的事,比如牛顿定理、法拉第、欧拉、XXX彗星等等。 里氏替换原则 有两种定义: 第一种定义:If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 is substitued for o2 then S is a subtype of T.(如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型S 是类型T的子类型。)

第二种定义:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.(所有引用基类的地方必须能透明地使用其子类的对象。)

用通俗的话讲,就是 所有父类能出现的地方子类就可以出现,并且替换为子类也不会产生任何的错误或异常,使用者可能根本就不需要知道是父类还是子类。要做到这样,那么子类就只能扩展父类的功能,但不能修改父类原有的功能。

这一原则主要是为了规范面向对象语言的** 继承 ** 这一特性。

里氏替换原则为良好的继承定义了一个规范,其包含了4层含义:

  • 1.子类可以实现父类的抽象方法,但是不能覆写父类的非抽象方法。
  • 2.子类可以添加自己特有的属性或者方法。
  • 3.子类覆写或者实现父类的方法时,输入的参数应该比父类的参数条件更宽松。
  • 4.子类覆写或实现父类的方法时,返回的结果应该比父类的返回结果更严格。

提醒

  1. 在使用父类的地方可以替换为子类,但是反过来在使用子类的地方却不一定能替换成父类。
  2. 如果父类的某些方法在子类中发生了“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合灯关系代替继承。

4.依赖倒置原则(Dependence Inversion Principle)

依赖倒置原则的原始定义是: High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions. 包含了三层含义:

  • 高层模块不应该依赖低层模块,两者都应该依赖其抽象;
  • 抽象不应该依赖细节;
  • 细节应该依赖抽象。

高层模块和低层模块很容易理解,每一个逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑就是低层模块,原子逻辑的再组装就是高层模块。那什么是抽象?什么又是细节呢?在Java 语言中,抽象就是指接口或者抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化。在OC 中,抽象就是协议啦,细节就是实现协议的类。 依赖倒置原则在Java 语言中的表现就是:

  • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
  • 接口或抽象类不依赖实现类;
  • 实现类依赖接口或抽象类。

具体到写代码时,那就是在使用到具体类时,不直接使用具体类,而使用具体类的抽象类或接口代替。

5.接口隔离原则(Interface Segregation Principle,ISP)

接口隔离原则有两种定义:

  • Clients should not be forced to depend upon interfaces that they don't use.(客户端不应该依赖它不需要的接口。)
  • The dependency of one class to another one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上。)

每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。 举个例子就是如果接口A 中有10个接口,而实现类B 使用到了接口A 中的 5个,实现类C 使用到了接口A 中的另外 5个,那么我们应该将接口A 拆分成接口A1和接口A2。然后让实现类B 实现接口A1中的接口,实现类C 实现接口A2中的接口。 错误的设计如下图所示:

修改前(错误的设计)

经过修改后的关系如下:

修改后(更好的设计)

将一个臃肿的接口拆分为两个独立的接口,所依赖的原则就是接口隔离原则,使用多个隔离的接口,比使用一个臃肿的接口要好的多。很多IM SDK都遵守了这种原则来使某些实现来具有单聊、群聊、音视频通话 等功能。可以参考容量、云之讯、网易云信等SDK。

接口隔离原则是对接口进行规范的约束,其包含了4层含义:

  • 1.接口要尽量小,但是也有一定的限度。因为过度小的接口会使接口变多,让程序变得复杂(我们总不能把每一个接口方法都定义在一个接口类里面吧)。
  • 2.接口要高内聚。高内聚可以提高接口、类、模块的处理能力,减少对外的交互。具体到实际开发,就是在接口中尽量少公布 方法。
  • 3.为依赖接口的类定制服务,只暴漏给它需要的方法,它不需要的方法则隐藏起来。
  • 4.接口的设计要有限度。接口的设计粒度越小,系统越灵活,这是不争的事实。但是,灵活的同时也使得接口类增加,是项目接口变得复杂。所以我们设计接口时,要把握一个“度”,接口不能太臃肿,也不能太小。

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

迪米特法则也称为最小知识原则(Principle of Least Knowledge,PLK)。 一个对象应该对其他对象有最少的了解。简单说来,就是一个类应该对自己需要耦合或调用的类知道得最少,被耦合或调用的类的内部是如何复杂与我都没关系,我只关系呗耦合或被调用的类提供给我的方法。 迪米特法则还有一个英文解释:Only talk to your immediate friends。 每个对象都会与其他对象有耦合关系,两个对象之间的耦合就成为了朋友关系。这种关系的类型有很多,例如组合、聚合、依赖等。 简单的说,只要两个类之间有交互或关联,那么它们就是朋友关系。

7.合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)

合成/聚合复用原则经常又叫做合成复用原则,它的设计原则是:要尽量使用合成/聚合,尽量不要使用继承。也就是说,我们要优先考虑使用合成、聚合来实现功能,在使用合成、聚合无法实现的情况下,才考虑使用继承来实现。

其实这里最重要的地方就是区分“has-a”和“is-a”的区别。 相对于合成和聚合,继承的缺点在于:父类的方法全部暴露给子类。父类如果发生变化,子类也得发生变化;聚合的复用的时候就对另外的类依赖的比较的少。

其他的设计原则整理

在《Head First 设计模式》一书中整理的设计原则有:

  • 封装变化
  • 多用组合,少用继承
  • 针对接口编程,不针对实现编程
  • 为交互对象之间的松耦合设计而努力
  • 类应该对扩展开放,对修改关闭。
  • 依赖抽象,不要依赖具体类。
  • 只和朋友谈
  • 别找我,我会找你。
  • 类应该只有一个改变的理由。

可以看出这里的设计原则其实也是用更通俗简单的话描述了上面的7大原则,或者扩展等。正所谓一千个读者眼中就有一千个哈姆雷特,我们不应该拘泥于多少个设计原则或者设计模式,应该将重点放在如何设计出高内聚,低耦合,代码能够高度复用,具有高度可维护性,健壮的程序上。毕竟这些原则或模式都是为了我们设计程序代码,实现某些功能服务的,不是吗?

参考: 百度百科-设计模式 http://www.runoob.com/design-pattern/design-pattern-intro.html http://www.uml.org.cn/sjms/201211023.asp#6 书籍: 《Head First 设计模式》 《设计模式 - 可复用的面向对象软件元素》 《设计模式之禅》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏企鹅号快讯

给 iOS 开发者的 python 学习日记十二

写在前面 我们在昨天的学习笔记讨论了 Python 基本变数类型与资料结构可以应用的属性或方法,除了基本的资料结构以外,你是否还记得 Python 可以透过引入...

21650
来自专栏陈树义

大白话说Java动态代理:一个面包店的动态代理帝国

代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位。代理模式从类型上来说,可以分为静...

34040
来自专栏北京马哥教育

Python面试必须要看的15个问题

引言 想找一份Python开发工作吗?那你很可能得证明自己知道如何使用Python。下面这些问题涉及了与Python相关的许多技能,问题的关注点主要是语言本身...

46890
来自专栏黑泽君的专栏

名词小解

----------------------------------------------------------------------------- py...

7400
来自专栏Python区块链

想到阿里巴巴从事Python吗?年薪300K不是梦!

作为Python工程师,进入大公司是开启职业新起点的关键,在阿里巴巴面试Python工程师的题目和经历,希望对广大Python工程师的求职者有一个帮助。

27560
来自专栏.NET技术

面向对象基础

通过封装、继承、多态把程序的耦合度降低,用设计模式使程序更加的灵活,容易修改,并且易于复用。缩短开发周期,重用性和继承性高,降低了重复的工作量,但缺点是,程序的...

10220
来自专栏Java面试笔试题

Java和JavaSciprt比较

JavaScript 与Java是两个公司开发的不同的两个产品。Java 是原Sun Microsystems公司推出的面向对象的程序设计语言,特别适合于互联网...

10130
来自专栏机器之心

在Python 2.7即将停止支持时,我们为你准备了一份3.x迁移指南

35140
来自专栏微信公众号:Java团长

Java回调机制解读

在一个应用系统中,无论使用何种语言开发,必然存在模块之间的调用,调用的方式分为几种:

12510
来自专栏CDA数据分析师

怎么样才算是精通 Python?

CDA专题线上活动“Python Week”即将上线,一大波Python技能马上来袭,敬请期待! 本文是对知乎问题“怎么样才算是精通 Python?”的回答,作...

50780

扫码关注云+社区

领取腾讯云代金券