前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式中的设计原则

设计模式中的设计原则

作者头像
Haley_Wong
发布2018-08-22 10:55:21
7300
发布2018-08-22 10:55:21
举报

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

翻了三本书《设计模式之禅》、《设计模式:可复用的面向对象软件元素》、《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 设计模式》 《设计模式 - 可复用的面向对象软件元素》 《设计模式之禅》

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016.11.22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 先来抛一个问题,设计模式到底有几个原则?
  • 设计原则
    • 1.开闭原则(Open Closed Principle,OCP)
      • 2.单一职责原则(Single Responsiblity Principle ,SRP)
        • 3.里氏替换原则(Liskov Substitution Principle,LSP)
          • 4.依赖倒置原则(Dependence Inversion Principle)
            • 5.接口隔离原则(Interface Segregation Principle,ISP)
              • 6.迪米特法则(Law of Demeter, LoD)
                • 7.合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
                • 其他的设计原则整理
                相关产品与服务
                实时音视频
                实时音视频(Tencent RTC)基于腾讯21年来在网络与音视频技术上的深度积累,以多人音视频通话和低延时互动直播两大场景化方案,通过腾讯云服务向开发者开放,致力于帮助开发者快速搭建低成本、低延时、高品质的音视频互动解决方案。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档