设计模式之基础篇¶
认识「设计模式」¶
什么是「模式」¶
- C. Alexander《建筑的永恒之道》: “一些问题及其解决方案不断变换面孔「重复出现」,其后是「共同的本质」。这就是模式。”
-
模式三部分
- Context:问题所在的上下文(Context),即当前模式所面对的此类问题所在的周围环境和状况,也就是说模式在什么状况下发生作用;
- Motivation:动机(Motivation),即此模式的目的或预期的目标是什么;
- Solutions:解决方案(Solutions),即为达到预期目标或解决此类问题所采用解决方案的核心。
-
模式的实质,就是从不断重复出现的事件中发现和抽象出的规律,是解决问题所形成的「经验的高度归纳、总结」。
什么是「设计模式」¶
- “每一个模式描述了一个在我们周围 「不断重复」 发生的问题,以及该问题的 「解决方案」 的核心。这样你就能一次又一次地使用该方案,而不必做重复劳动。”
- 上世纪 50年代 C. Alexander 《建筑的永恒之道》
- 关键词:不断重复(问题的重复性)、解决方案
软件领域的设计模式¶
- 上世纪 90 年代最早从建筑领域引入软件工程领域 by GoF
- 《Design Patterns: Elements of Reusable Object-Oriented Software》
- 《设计模式:可复用面向对象软件的基本元素》
- 关键词:可复用(目标)、 面向对象(手段)
- 此书阐明了设计模式的目标 : 复用。
复杂性管理与「面向对象」¶
从「面向对象」谈起¶
-
两种思维方式
- 当程序员面对一个复杂问题时的两种思维方式:机器思维和抽象思维;两种都很重要,本文重点关注第二种。
-
机器思维(向下分解)
- 如何把握机器底层原理,从微观理解对象构造
- 如语言构造、编译转换、内存模型、运行时机制等。
-
抽象思维(向上抽象)
- 如何将周围世界抽象为程序代码。
- 如组件封装、面向对象、设计模式、架构模式等
-
两个视角深入理解「面向对象」
-
三大特性(向下理解)
- 封装,隐藏内部实现
- 继承,复用现有代码
- 多态,改写对象行为
-
抽象意义(向上理解)
- 深刻把握面向对象机制所带来的抽象意义,理解如何这些机制来表达现实世界,掌握什么是「好的面向对象设计」。
-
软件设计复杂的根本原因¶
- 软件设计复杂的根本原因:变化。
- 客户需求变化
- 技术平台变化
- 开发团队变化
- 市场环境变化
- ......
如何管理复杂性?¶
-
思维模型
-
分解思维
- 常见做法: 分而治之,将大问题「分解」为多个小问题,将复杂问题分解为多个简单问题。
-
抽象思维
- 由于不能掌握全部的复杂对象,我们选择 忽视它的非本质细节 ,而去处理「泛化和理想化」了的对象模型。
-
-
结构化(SD) vs 面向对象(OOD)
-
诞生背景
- OOD 和 SD (Structured Design,结构化设计)的概念几乎同时诞生,分别以不同的方式来表现数据结构和算法。
- NATO 会议采纳了Dijkstra的思想,整个软件产业都同意 goto 语句的确是有害的,结构化方法、瀑布模型从70年代开始大行其道。
- 到90年代,OOD突然风靡了整个软件行业。
-
SD的问题
- SD中模块被组织成一个树型结构,棵树的根就是主模块。
- 顶端模块关心规模最大的问题,负责最重要的策略;底层模块只实现最小的细节。
- 体系结构中越靠上,概念的抽象层次就越高,也越接近问题领域。
- 但是,由于上方的模块需要调用下方的模块,所以这些 「上方的模块就依赖于下方的细节」。 也就是说,当实现细节变化时,抽象也会受到影响。
-
OOD的精髓
- 我们希望 倒转这种依赖关系:我们创建的抽象不依赖于任何细节,而细节则高度依赖于上面的抽象。
- 依赖关系的倒转正是 OOD 和传统 SD 之间根本的差异,也正是 OOD 思想的精华所在。
-
-
Show Me the Code
-
SD(分解)
-
OOD(抽象)
-
结论:用面向对象设计的方式,能更好的抵御软件的变化所造成的影响。
-
理解「设计原则」¶
前文总结:为什么要面向对象设计¶
- 软件设计复杂性的根本原因: 变化。
- 「变化」是「复用」的天敌,而「面向对象」设计的优势在于:抵御变化。
- 不是说有了变化,「面向对象」设计能完全避免做任何改变,而是它能够将变化引起的改变降到最小。
-
宏观上
- 面向对象的构建方式更能适应软件的变化;
- 「隔离」变化,将变化带来的影响降到最小。
-
微观
- 面向对象更强调各个类的的责任;需求变化所导致的新增类不应该影响原来类的实现,所谓各司其职、各负其责。
- 即「各司其职」, 更强调各个类的的责任。
-
重新理解什么是对象
- 语言实现层面:封装了代码和数据的结构。
- 规格层面:可被使用的公共接口。
- 抽象层面:某种拥有责任的抽象。
依赖倒置原则¶
- 依赖倒置原则 (Dependence Inversion Principle, DIP)
- 高层模式(稳定)不应该依赖底层模(变化)块,两者都应依赖其抽象(稳定);
- 抽象(稳定)不应该依赖实现细节(变化),细节应该依赖抽象(稳定)。
- 说人话: 面向接口编程,而不是面向实现类。
开放封闭原则¶
- 开放封闭原则 (Open Closed Principle, OCP)
- 软件实体应当对扩展开放,对修改关闭。
- 说人话: 扩展新类,而不是修改旧类。
- 合成复用原则、里氏替换原则都是开闭原则的具体实现规范。
单一指责原则¶
- 单一指责原则 (Single Responsibility Principle, SRP)
- 一个类应该有且一个引起它变化的原因,否则类应该被拆分。
- 变化的方向隐含着类的责任。
- 说人话: 每个类只负责自己的事情,而不是变成万能。
里氏置换原则¶
- 里氏置换原则 (Liskov Substitution Principle, LSP)
- 继承必须确保,超类所拥有的性质在子类中仍然成立。
- 子类必须能够替换他们的基类(IS-A)。
- 说人话: 继承父类而不去改变父类。
接口隔离原则¶
- 接口隔离原则 (Interface Segregation Principle, ISP)
- 不应该强迫客户程序依赖他们不用的方法。
- 接口应用小而完备。
- 一个类对另一个类的依赖应该建立在最小接口上
- 说人话: 各个类建立自己的专用接口(多个),而不是建立万能接口(一个)。
合成复用原则¶
- 又叫组合/聚合复用原则(Composite/Aggregate Reuse Principle, CARP)
- 软件复用时,要尽量先使用组合或者聚合等关系来实现,其次才考虑使用继承关系来实现。
- 说人话: 优先对象组合,其次类继承。
- 继承在某种程度上破坏类封装性,子类父类耦合度高;对象组合则只要求被组合的对象具有良好的定义,耦合度低。
- 合成复用原则(Composite Reuse Principle, CRP)
迪米特法则¶
- 迪米特法则 (Law of Demeter, LoD)
- 最少知识原则(Least Knowledge Principle, LKP)
- 只与你的直接朋友交谈,不跟"陌生人”说话。Talk only to your immediate friends and not to strangers.
- 说人话: 无需直接交互的两个类,如果需要交互,使用中介者。
封装变化点¶
- 使用封装来创建对象之间的分界层,一侧进行修改 (变化),另一侧不受影响(稳定),从而达到松耦合。
面向接口设计¶
- 任何时代任何领域,产业强盛的标志:接口标准化。
- 1)秦国为何能一统六国? 兵器接口标准化 、货币、道路等
- 2)雕版印刷 vs 毕升的活字印刷
非面向对象设计原则¶
-
DRY、KISS、YAGNI三原则
- SOLID,GRASP设计原则:适用于面向对象设计;
- DRY、KISS、YAGNI 软件三原则:适用于在软件设计的各个层面的。它不仅适用于面向对象的设计,也适用于面向过程的程序设计;不仅适用于类的设计,也适用于模块、子系统的设计;就连在项目架构运维部署中,也适用这套简单的法则。
-
DRY - Don't Repeat Yourself
- 第一条准则是千万不要重复你自身。
- 尽量在项目中减少重复的代码行,重复的方法,重复的模块。其实,许多设计原则和模式最本质的思想都是在消除重复。我们常提起的 重用性 和 可维护性 其实是基于减少重复这一思想。
- 使其每一个部件都是职责明确的并且可重用的。
-
KISS - Keep It Simple & Stupid
- 第二条准则是保持简单易懂。
- 高手高就高在可以将复杂的东西“简单”的实现出来。
- 们应该致力于代码的 可理解性;降低复杂度也意味着维护变得简单。
- Martin Flower在《重构》中有一句经典的话:"任何一个傻瓜都能写出计算机可以理解的程序,只有写出人类容易理解的程序才是优秀的程序员。“
-
YAGNI - You Ain’t Gonna Need It
- 第三条准则是你将不会需要它。千万不要进行过度设计。
- 一些设计是否必要,更多的应该基于当前的情况。而不是为了应对未来的各种变化,画蛇添足的设计。
- 因为创业公司的时间是非常宝贵的,早一步推出新的服务就能抢占先机;过度设计往往会延缓开发迭代的速度。
从原则到经验¶
将设计原则提升为设计经验¶
-
设计习语 Design Idioms
- 描述与特点语言相关的低层模式、技巧、惯用法
-
设计模式 DesignPattern
- 主要描述 「类与相互通信的对象之间」 的组织关系,包括它们的角色、职责、协作方式等。
- 主要解决: 变化中的复用性 。
-
架构模式 Architectural Pattern
- 描述 系统 中与基本结构组织关系密切的高层模式,子系统的划分、责任,以及如何组织它们之间关系的规则。
Refactoring to Patterns¶
- 现代软件设计的特征是 「需求的频繁变化」。设计模式的要点是 「寻找变化点,然后在变化点处使用设计模式 ,从而更好应对需求变化」。
- 所谓「好的面向对象设计」,指那些可以满足 「应对变化,提高复用」 的设计。
- 设计模式的应用不宜先入为主,没有一步到位的设计。敏捷开发的提倡 「Refactoring to Patterns」,是目前公认的最好应用设计模式的方法。
重构关键技法¶
- 静态 -> 动态
- 早绑定 -> 晚绑定
- 继承 -> 组合
- 编译时依赖 -> 运行时依赖
- 紧耦合 -> 松耦合
UML与依赖¶
什么是 UML¶
- Unified Modeling Language,统一建模语言,用来设计软件模型的图形化建模语言。
- 9种图:用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图。
类图概述¶
- 类图(Class Diagram), 显示了模型的静态结构,包括类、类的内部结构和类之间的关系。
- 类图是系统分析和设计阶段的产物。
「类」的表示¶
- 类名、属性、方法
-
属性/方法的可见性
+
:表示 public-
:表示 private#
:表示 protected
-
属性:
可见性 名称:类型 [= 默认值]
- 方法:
可见性 名称(参数列表)[; 返回类型]
「类之间关系」的表示法¶
-
关联关系
- 图例:带箭头的实线
- 表示:类与类之间的引用关系,包括:一般关联关系,聚合关系和组合关系。
- 单向关联、双向关联、自关联
-
聚合关系
- 图例:带空心菱形的实线,菱形指向整体
- 表示:整体与部分之间的关联关系,属于强关联关系。
- 通过成员对象实现,成员对象可以脱离整体对象而独立存在。如 School <- Teacher。
-
组合关系
- 图例:带实心菱形的实线,菱形指向整体
- 表示:类之间的整体与部分之间的关联关系,一种更强烈的聚合关系。
- 整体可以控制部分的生命周期,部分对象不可脱离整体对象而存在。如 Head <- Mouth。
-
依赖关系
- 图例:带箭头的虚线,箭头指向被依赖对象。
- 表示:一种使用关系,临时性的关联, 最弱关联关系。
- 通常表现为:局部变量、方法的参数、或对被依赖对象的静态方法调用。
-
继承关系(Generalization)
- 图例:带空心箭头的实线,箭头指向父类。
- 表示:父类与子类,一般与特殊,泛化或继承,对象间 最强的耦合关系。
-
实现关系
- 图例:带空心箭头的虚线,箭头指向接口。
- 表示:接口与实现类之间的关系。
-
耦合性依次递增:依赖 < 关联 < 聚合 < 组合 < 继承 < 实现