设计模式之设计原则

image.png

“开一闭” 原则(OCP)

经典力学的基石是牛顿三大定律。 而面向对象的可复用设计 (Object Oriented Design, 或 OOD) 的第一块基石,便是所谓的”开-闭“原则 (Open-Closed Principle, 常缩写为OCP)。

“开-闭 ” 原则讲的是:一个软件实体应当对扩展开放, 对修改关闭。 这一原则最早由 Bertrand Meyer [MEYER88]提出, 英文原文是:Software entities should be open for extension, but closed for modification.

这个原则说的是, 在设计一个模块的时候, 应当使这个模块可以在不被修改的前提下被扩展。 换言之, 应当可以在不必修改源代码的情况下改变这个模块的行为。

所有的软件系统都有一个共同的性质, 即对它们的需求都会随时间的推移而发生变化。 在软件系统面临新的需求时, 系统的设计必须是稳定的。 满足 “开-闭” 原则的设计可以给一个软件系统两个无可比拟的优越性:

  • 通过扩展已有的软件系统, 可以提供新的行为, 以满足对软件的新需求, 使变化中的软件系统有一定的适应性和灵活性。
  • 已有的软件模块,特别是最重要的抽象层模块不能再修改, 这就使变化中的软件系统有一定的稳定性和延续性。

具有这两个优点的软件系统是一个在高层次上实现了复用的系统, 也是一个易于维护 的系统。

里氏代换原则(LSP)

从“开-闭 ” 原则中可以看出面向对象设计的重要原则是创建抽象化,并且从抽象化导出具体化。 具体化可以给出不同的版本, 每个版本都给出不同的实现。
从抽象化到具体化的导出要使用继承关系和这里要引入的里氏代换原则 (Liskov Substitution Principle, 常缩写为 LSP) 。 里氏代换原则由 Barbara Liskov 提出。

里氏代换原则的严格表达是:
如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以Tl定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型。

换言之,一个软件实体如果使用的是一个基类的话,那么一定适用于其子类,而且它根本不能察觉出基类对象和子类对象的区别。

比如,假设有两个类,一个是Base类,另一个是Derived类,而且Derived类是Base类的子类,那么一个方法如果可以接受一个基类对象b的话:

method1(Base b)

那么它必然可以接受一个子类对象d,也即可以有methodl(d)。
里氏代换原则是继承复用的基石。只有当衍生类可以替换掉基类,软件单位的功能不会受到影响时,基类才能真正被复用,而衍生类也才能够在基类的基础上增加新的行为。

反过来的代换不成立。

依赖倒转原则(DIP)

实现 “ 开-闭 ” 原则的关键是抽象化, 并且从抽象化导出具体化实现。 如果说 “ 开-闭 ” 原则是面向对象设计的目标的话, 依赖倒转原则就是这个面向对象设计的主要机制[MARTIN00] 。

依赖倒转原则讲的是: 要依赖于抽象, 不要依赖于具体.

简单地说, 依赖倒转原则 (Dependence Inversion Principle) 要求客户端依赖于抽象耦合。 依赖倒转原则的表述是:

抽象不应当依赖于细节,细节应当依赖于抽象。
(Abstractions should not depend upon details. Details should depend upon abstractions)

依赖倒转原则的另一种表述是:

要针对接口编程, 不要针对实现编程。
(Program to an interface, not an implement­ation)

第二种表述是[GOF95]一书所强调的。
针对接口编程的意思就是说, 应当使用 Java 接口和抽象 Java 类进行变量的类型声明、参量的类型声明、 方法的返还类型声明, 以及数据类型的转换等。

不要针对实现编程的意思就是说, 不应当使用具体 Java 类进行变量的类型声明、 参 量的类型声明、 方法的返还类型声明, 以及数据类型的转换等。

要保证这一点,一个具体Java类应当只实现Java接口和抽象Java类中声明过的方法,而不应当给出多余的方法。

倒转依赖关系强调一个系统内的实体之间关系的灵活性。基本上,如果设计师希望遵循”开-闭“原则,那么倒转依赖原则便是达到要求的途径。

接口隔离原则(ISP)

接口隔离原则 (Interface Segregation Principle, 常常略写做 ISP) 讲的是: 使用多个专门的接口比使用单一的总接口要好。
换言之, 从一个客户类的角度来讲: 一个类对另外一个类的依赖性应当是建立在最小的接口上的。

人们所说的 “接口” 往往是指两种不同的东西: 一种是指 Java 语言中的有严格定义的 Interface 结构, 比如java.lang.Runnable 就是一个 Java 接口;另一种就是一个类型所具有的方法特征的集合,也称做 “接口”,但仅是一种逻辑上的抽象。

应于这两种不同的用词, 接口隔离原则的表达方式以及含义都有所不同。

过于臃肿的接口是对接口的污染(InterfaceContamination)。

与迪米特法则的关系
迪米特法则要求任何一个软件实体,除非绝对需要,不然不要与外界通信。即使必须进行通信,也应当尽量限制通信的广度和深度。

显然,定制服务原则拒绝向客户端提供不需要提供的行为,是符合迪米特法则的。

合成/聚合复用原则 (CARP)

合成/聚合复用原则 (Composite/Aggregate Reuse Principle, 或 CARP) 经常又叫做合成复用原则 (Composite Reuse Principle 或 CRP) 。 合成/聚合复用原则就是在一个新的对象里面使用一些已有的对象, 使之成为新对象的部分;新的对象通过向这些对象的委派达到复用已有功能的目的。

这个设计原则有另一个更简短的表述: 要尽量使用合成/聚合, 尽量不要使用继承。

合成 (Composite) 一词的使用很广泛, 经常导致混淆。 为避免这些混淆, 不妨先来考察一下 “ 合成” 与 “聚合” 的区别。

合成和聚合的区别

合成 (Composition) 和聚合 (Aggregation) 均是关联 (Association) 的特殊种类。 聚合用来表示 “拥有” 关系或者整体与部分的关系;而合成则用来表示一种强得多的 “拥有” 关系。 在一个合成关系里, 部分和整体的生命周期是一样的。 一个合成的新的对象完全拥有对其组成部分的支配权, 包括它们的创建和湮灭等。 使用程序语言的术语来讲, 组合而成的新对象对组成部分的内存分配、 内存释放有绝对的责任。

更进一步来讲, 一个合成的多重性 (Multiplicity) 不能超过1, 换言之, —个合成关系中的成分对象是不能与另一个合成关系共享的。 一个成分对象在同一个时间内只能属于一个合成关系。 如果一个合成关系湮灭了, 那么所有的成分对象要么自己湮灭所有的成分对象(这种情况较为普遍), 要么就得将这责任交给别人(这种情况较为罕见)。

用 C 程序员较易理解的语言来讲, 合成是值的聚合 (Aggregation by Value), 而通常所说的聚合则是引用的聚合 (Aggregation by Reference) 。

迪米特法则(LoD)

迪米特法则 (Law of Demeter 或简写为 LoD) 又叫做最少知识原则 (Least Knowledge Principle 或简写为 LKP), 就是说,一个对象应当对其他对象有尽可能少的了解。

迪米特法则最初是用来作为面向对象的系统设计风格的一种法则, 于 1987 年秋天由Ian Holland 在美国东北大学 (Northeastern University) 为一个叫做迪米特 (Demeter) 的项目设计提出的, 因此叫做迪米特法则[LIEB89] [LIEB86] 。 这条法则实际上是很多著名系统, 比如火星登录软件系统、 木星的欧罗巴卫星轨道飞船的软件系统的指导设计原则。

迪米特法则的各种表述

没有任何一个其他的OO设计原则像迪米特法则这样有如此之多的表述方式, 下面给出的也只是众多的表述中较有代表性的几种:

  • 只与你直接的朋友们通信 (Only talk to your immediate friends)。
  • 不要跟 “ 陌生人” 说话 (Don't talk to strangers)。
  • 每一个软件单位对其他的单位都只有最少的知识, 而且局限于那些与本单位密切相关的软件单位。

在上面的表述里面, 什么是 “ 直接 ”、”陌生” 和 “ 密切 ” 则被有意地模糊化了, 以便在不同的环境下可以有不同的解释。

狭义的迪米特法则

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

狭义的迪米特法则的缺点

遵循狭义的迪米特法则会产生一个明显的缺点:会在系统里造出大量的小方法,散落在系统的各个角落。这些方法仅仅是传递间接的调用,因此与系统的商务逻辑无关。当设计师试图从一张类图看出总体的架构时,这些小的方法会造成迷惑和困扰。

为了克服狭义的迪米特法则的缺点,可以使用依赖倒转原则,引入一个抽象的类型引用“抽象陌生人”对象,使“某人”依赖于“抽象陌生人”。换言之,就是将“抽象陌生人”变成朋友。

参考资料

《Java与模式》


个人介绍:

高广超:多年一线互联网研发与架构设计经验,擅长设计与落地高可用、高性能、可扩展的互联网架构。

本文首发在 高广超的简书博客 转载请注明!

简书博客
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267