设计模式精解
2021-05-13 15:23:41 0 举报
AI智能生成
《设计模式精解》从一个新的视角描述面向对象设计,将面向中对象编程的原则与运用设计模式力量创建健壮、可靠的软件开发环境结合起来。书中采用实用、恰当的例子,指导读者用模式解决普通的编程问题,并且解释现代软件设计模式的优越性。《设计模式精解》适用于学习面向中对象设计和设计模式的学生、程序员以及从事软件开发的人士。
作者其他创作
大纲/内容
用模式的方法来思考
面向模式设计
将注意力集中在高层关联上
从片段开始设计不是一个好的设计方法
想把预先形成的片段累加起来以构造任何拥有自然特征的事物,这是不可能的
将预先成型的部分添加在一起是不能得到优秀的设计的
根据在整体中的位置来定义每个部分
各部分需要与众不同,这样它们可以利用自己独特的环境
在场景中设计片段
每个部分都将根据其在更大的整体环境中的存在而有其特定的形式
从最简单的角度观察问题,然后添加附加的特征(差别)逐渐让设计变得复杂,因为我们加入了更多信息
每个模式都是对空间进行区别的操作即,在以前没有差异的地方创建差异
模式独立于任何人而存在
设计遵循的原则
模式应该按照顺序,一个一个使用
场景优先,首先使用那些为其他模式创造场景的模式
基于模式的设计途径
1. 为了理解需要实现的目标,从对整体的概念性理解开始
2. 识别出在整体中出现的模式
3. 从为其他模式创造场景的模式开始
4. 应用这些模式
5. 对剩下的模式和中途发现的模式,重复步骤3~5
6. 每次一个地应用这些模式以创建场景,在这个场景中对设计进行精炼
标准的设计方法经常让我们只见树木,不见森林,因为我们过分将精力集中于系统的细节上--类
在系统决策过程中,我们经常陷入细节中而忘记了系统最大的场景
细节会成为最大的视图周围的一片阴云,让开发者集中精力于小的,局部的决策
模式给了你一种语言,让你得以超越细节
用模式解决问题
1. 发现我在问题领域中拥有的模式。这些是需要分析的模式
2. 对于需要分析的模式
挑选出为其他模式提供最多场景的模式
在概念性最高的设计中使用这个模式
识别任何可能出现的附加模式
3. 按需将细节添加到设计中,扩展方法和类定义
基本工具
寻找场景
首先关注对象之间的关联
假设在需要的时候可以构造出适合这些关联的对象
推迟考虑如何实例化我需要的对象
尽量减少脑子里考虑的事情的数量
考虑你在系统中需要什么,然后再关心如何实现它
最高级模式约束其他的模式
设计模式的原则和策略
模式在局部和全局范围的作用
模式的机制以及作为它们基础的原则和策略
开放-封闭的原则
对扩展是开放的
对更改是封闭的
将软件设计成这样:在不修改代码的前提下对我们的软件进行扩展
例:Bridge模式中就可以在不修改任何现存的类的前提下加入新的实现部分
从场景进行设计的原则
在设计片段出现的细节之前先创建整体视图
识别可能性不等于必须要跟着可能性走
可能性给了我对问题领域的洞察力
设计模式帮助我看到变化可能在哪里发生,而不是哪个特定的变化将发生
在不知道模式将被怎样使用的前提(即不知道它的场景)下就试图决定实现它,是傻瓜才会作的事情
怎样作出设计决策
通常没有哪个实现方案天生就比另一个更好
在哪种场合下这种选择会比另一种选择更好?
这些场合哪些和我的问题领域最相似?
包容变化的原则
我的继承体系中,类的深度很少超过两层
决不让一个类包含两件变化并以某种方式耦合在一起的事物
模式有效包容变化
模式识别变化之间的关联
通过包容变化,可以接纳未来可能出现的变化
通过适当包容变化,我可以只实现那些我需要的特性,而不必牺牲未来的能力
"试图确定并接纳所有的变化"通常并不能造就好的系统--这种做法根本不能造就系统,这叫作分析瘫痪
用设计模式处理变化
不同的模式会以相似的形式处理变化点和新的需求
具体模式
Strategy
最后的灾难往往来源于短期行为中不太好的决策
我们关注当下的利害关系而忽略长期问题
"为变化进行设计"的开销>"不考虑变化进行设计" ?
预料到变化将会发生,并观察它们将在哪里发生,但不会预测变化确切的种类
考虑你的设计中哪些是可变的
与关注引起重新设计的原因相反
不是考虑什么会使你的设计改变
考虑什么会变化却又不引起重新设计
封装变化的概念
提高内聚度,对灵活性有帮助
在一个抽象类中封装算法并交替使用这些算法的模式就是Strategy模式
意图:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。使算法可以独立于使用它的客户而变化。
原则
对象拥有责任
责任的不同特定实现通过使用多态来表现
将问题领域中发生的行为彼此分离,即,使它们解耦
将算法的选择和算法的实现相分离,让客户可以根据场景作出选择
封装业务规则
参与者和协作者
Strategy
ConcreteStrategies
Context
Decorator
根据责任进行分解
如何实现提供新功能的对象
如何为每种特定情况将对象组织起来
将"Decorator对象的实现"与"判断如何使用它们的对象"相分离
每个Decorator对象只关心自己添加的功能,而不关心自己如何被添加到对象链中
典型实现方式
使用工厂模式,在某些配置信息的基础上实例化对象链
解决方案: 扩展一个对象的功能而不必借助于子类化
意图:为一个对象动态连接附加的职责
参与者/协作者
Component
ConcreteComponents
Decorator
Singleton/Double-Checked Locking
Singleton-单线程
Double-Checked Locking-多线程
Observer
意图:在对象间定义一对多的依赖关系
问题:当一个对象发生变化需要向一系列对象发出通知,而这个对象的列表是不断变化的
解决方案:将监视某个事件的责任委托给一个中心对象,Subject
参与者/协作者
Subject
Observer
为了能让所有的观察者型 对象实现Observer接口,有时需要和Adapter模式
不是用于所有的依赖关系而是用于变化的或动态的依赖关系
一个观察者可能只需要处理事件的某种特定情况
观察者将另外的通知过滤掉
将过滤通知的责任转移给Subject,结合Strategy模式每个观察者在注册时都将合适的策略对象交给Subject对象
Template Method
Template Method不是组合在一起的Strategy模式
几个Stragety模式彼此连接出现的情况很少见,那样的设计会使灵活性降低
先定义步骤的顺序,然后重载那些需要改变的步骤
Factory Method
意图:定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类
在定义框架的时候很常用
框架存在于一个抽象的层次上
框架不知道,也不应该关心特定对象的实例化
将特定对象的实例化推迟给框架的用户
分析矩阵
问题不会是有条不紊而行为良好的
总会有一引起没有组织好的异常和变化出现,破坏我们精心设计的模型
弄清系统中的变化点然后分析识别我应该在设计中使用的模式
1.识别某种情况下最重要的特征,并将它们组织成一个矩阵。用矩阵表现的概念为每个特征作上标记
2. 识别其他的情况根据需要对这个矩阵进行扩展。独立于其他的情况来分别处理每种情况
3. 用新的概念扩展分析矩阵
4.用矩阵的行来确定规则
5.用矩阵的列来确定特殊情况
6. 从这种分析中确定模式
7.开发一个高层设计
分析矩阵不太可能捕捉一个问题领域中所有的问题特点
当客户给我太多特定情况,脑子中容不下整体视图时,分析矩阵就很有用了
终点和起点
面向对象原则的新视角,基于对设计模式的理解
对象是负有定义良好的责任的东西
对象对自己负责
封装意味着任何形式的隐藏
数据隐藏
类隐藏
隐藏在抽象类和接口后面
实现隐藏
使用共同点/变化点分析抽象出行为和数据中的变化点
针对接口进行设计
把继承考虑为一种封装变化的方法,而不是为现有对象制造特殊情况
把变化点封装在一个类中,并使之与这个类中的其他变化点相分离
力求松耦合,高内聚
绝对细心地应用"一次并且只有一次"规则
设计模式如何帮助我们封装实现
大多数模式提供了隐藏特定实现的途径
隐藏实现的价值
让开发者可以轻易地添加新的实现,因为客户不知道现在的实现是怎样工作的
共同点/变化点分析和设计模式,如何帮助我们理解抽象类
使用共同点/变化点分析来实现很多模式
寻找共同点可以帮助我们发现问题领域中出现的模式
根据涉及的责任对问题领域进行分解
共同点/变化点分析确定了我的概念视角和实现视角
如果只考虑共同点和使用共同点的对象可以从一个不同的角度来考虑问题--按照责任的分解
将问题领域分解为责任
然后定义实现这些责任需要的对象
设计者不应该在知道自己需要的所有对象之前担心如何实例化对象
特定的模式经常会协助我们考虑如何分解责任
确定对象之间的关联
一个模式描述了一个特定场景中特定问题的约束,动机和关联并给了我们一种方法论来讨论这些问题
设计模式和场景化设计
针对接口和多态进行设计
根据场景进行设计
抽象类的接口定义了场景它的所有派生类必须在这个场景内实现
学习模式的途径
这个模式中隐藏了什么实现?
从而让你可以改变实现
这个模式中出现了什么共同点?
这个模式中对象的责任是什么?
这些对象间有什么关联?
这个模式如何成为“根据场景进行设计”的范例?
观察模式的特点
它们封装什么?
它们如何使用共同点/变化点分析?
它们如何按照责任对问题领域进行分解?
它们如何确定对象之间的关联?
它们如何阐述场景化设计
书摘
对象真正的威力不是继承而是“行为封装”
针对接口进行设计
设计模式不是单独存在的,而是需要与其他设计模式协同工作以帮助你构建更健壮的应用程序
顿悟完全改变了我对设计模式的看法:设计模式无法作为独立的条款使用,我应该把设计模式放在一起使用
使用模式来帮助理解乃至描述问题领域,而不是仅仅在理解了问题领域以后使用模式来创建一个设计
基于模式的分析让你成为一个更有力,更高效的分析者。因为它们让你更抽象地处理你的模型,因为它们向你 展示许多其他分析者积累的经验。
避免过早进入实现阶段, 在做之前先想
功能分解是处理复杂问题的一种自然方法。使用功能分解的难题是:它不能帮助我们为未来可能发生的变化做准备,它不能帮助我们的代码优雅的演变
试图同时关注过多的东西,就等于邀请错误与变化同时到来
魔鬼就生活在副作用中。函数的一个重要问题就是可能导致很难发现的副作用
维护和调试的大多数时间不是花在修改错误上,而是花在寻找错误和考虑如何避免在修改中再次引发副作用
使用功能分解时,变化的需求会让我软件开发和维护的成果大受打击。我主要把精力集中在功能上。一个函数和数据结构的变化会影响到其他的函数和数据,于是受影响的部分也要修改。这就像一个滚下山的
面向对象软件设计基础
面向对象范式以前
功能分解
局限性
处理变化
事物总是在变化
用户需求的变化
开发者对需求理解的变化
软件开发环境的变化
分析做得再好,也无法知道用户的所有需求
低内聚,紧耦合
一个函数或数据结构的变化会影响到其他函数和数据
只关注功能,会导致“一处变化引起一连串变化”
传统的面向对象的观点:寻找名词,数据封装,焦点局限于如何实现对象
传统面向对象的局限性
过高的继承体系导致紧耦合,低内聚
过早地对细节投入过多的关心
处理细节总是比较容易
细节上的解决方案很明显
应该尽可能晚地投入到细节中
类爆炸
对继承的过度依赖会导致更高的维护代价
面向对象范式
责任的转移
每个人对自己负责,而不是由控制程序对他们负责
控制程序可以与不同类型的人对话
控制程序不需要知道学生在教室之间移动的具体步骤
使用对象将责任转移到更局部的范围
软件开发过程中的视角 by Martin Fowler
概念
规格
实现
从三个视角层次来考虑问题
在概念层次上通信而在实现层次上执行
请求者不知道具体发生了什么,只知道概念上发生了什么
对象
拥有责任的某种东西
对象应该对自己负责,并且这种责任应该被清晰地定义出来
很多东西不需要暴露给外界
概念层次:一个对象是一系列责任
规格层次:一个对象是一系列可以被其他对象或该对象自己调用的方法
实现层次:一个对象是一堆 代码和数据
类
抽象类
定义了一组类可以做的事情
在概念层次上定义抽象类
封装
向用户隐藏一些东西
好处
使用更容易
实现可以在不考虑调用者的情况下变更
对象外部的代码不知道对象内部的情况
减少副作用
让对象为自己负更多责任,控制程序需要负的责任更少
设计模式
产生于建筑学和人类学
"质量可以客观评价吗?"--Christopher Alexander
对于任何特定的建筑物,优秀的结构之间总有一些相同之处
需要解决的问题的特点中的共同点
结构不可以与它们要解决的问题分离
观察解决相似问题的不同方案
模式:在某一个情境下的问题解决方案
是什么让设计优秀?
是什么让设计拙劣?
模式可以帮助你提升思考的层次
具体模式
Facade
在原始系统的前面构造了一个新的前端接口
适用于
不需要使用原始复杂系统的所有功能
希望包装或隐藏原有系统
编写一个新的类的代价<< 让所有人学会使用该系统的代价
简化接口
Adapter
将接口转换成为一个现有的接口
Bridge
将抽象部分与实现部分分离,使得它们都可以独立变化
避免抽象部分与实现部分的紧耦合
过度使用继承
由于得到了新的锤子,所有的东西看起来都是钉子
根据对象的责任来考虑它们,而不是根据它们的结构
对象的特点应该基于它们的责任而不是它们包含的东西或它们是什么东西
寻找初期设计的替代品
避免”分析瘫痪“
将注意力集中于模式的场景
模式尝试解决的问题
在确切知道如何实现设计模式之前就判断出何时在问题领域中使用它们
共同点/变化点分析
一种发现对象的新范式比仅仅观察“名词/动词”更好的方法
共同点分析:寻找共同的元素,它们帮助我们理解“家庭成员在哪些方面相同”
元素通过怎样的共同点来定义它们的家族
寻找不太可能随时间变化的结构
变化点分析:“家族成员有什么不同”
只有在一个给定的共同点内,变化点才有意义
捕捉有可能变化的结构
只有在由相关的共同点分析定义的上下文中,变化点分析才有意义
从体系结构角度
共同点分析为体系结构提供了耐久性
变化点分析让它适应各种应用的需要
变化点是问题领域中的具体情况,共同点定义了问题领域中将具体情况捆绑在一起的概念
共同的概念由抽象类实现
变化点分析发现的变化将由具体类来实现
一条规则,一个地方
只在一个地方实现一条规则
如果你的程序中有做某一件事的规则,只实现它一次
一次并且只有一次
系统必须与你希望沟通的任何事物沟通
系统不能包含任何重复的代码
在这个模式里有一个抽象部分(以及派生类)和一个实现部分
设计实现部分的接口时,应该考虑它必须支持的抽象类的不同的派生类
关键特征
意图:将一组实现部分从另一组使用它们的对象中分离出来
问题:一个抽象类的派生类必须使用多种实现部分,但又不能引起类数量的爆炸
解决方案:为所有的实现部分定义一个接口,让抽象类的所有派生类使用这个接口
参与者与协作者
Abstraction为正在实现的对象定义接口
Implementor为特定的实现部分类定义接口
Abstraction的派生类使用Implementor的派生类而不必知道自己使用的特定的ConcreteImplementor
Abstract Factory
意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
Switch语句可能指出对抽象的需要
对多态行为的需要
抽象
存在着放错地方的责任
将责任交给另外的对象
跨接分析和设计的策略
发现并封装变化点
优先使用对象组合,而不是类继承
针对接口设计,而不是针对实现设计
创建优秀面向对象设计的策略
针对接口编程
优先使用对象组合而非继承
找到并封装变化点
扩展视野
对对象的看法
传统:一堆 数据和方法从实现视角对对象的观察
负有责任的某种东西基于概念视角的定义
将注意力集中在对象应该做什么而不仅仅是如何实现他们
分两步构建软件
建立一个初步的设计不必担心涉及的任何细节
实现上面的步骤得到的设计
按照责任来考虑问题可以简化问题
帮助定义对象的公共接口
如果对象有某种责任,就一定有某种途径要求它履行自己的责任
并不对对象内容做任何暗示
关注于动机而不是实现
对封装的看法
传统:隐藏数据
隐藏任何东西.封装可以被用于在行为中包含变化
任何形式的隐藏
数据的封装
方法的封装
子类的封装
抽象类
其他对象的封装
Adapter接口
提供了一种更好的切分程序的方式
封装层将成为要设计的接口
使用继承的方式
传统:特化和复用
为了复用的继承
对象分类
作为概念的继承
针对接口进行设计
一致地处理概念上相同的不同具体类
发现并封装变化点
考虑你的设计中哪些是可变的
使用抽象类的引用来进行组合
很多设计模式都使用封装来创建对象之间的分界层
设计者可以在分界层的一侧进行修改,而不会对另一侧产生影响,形成了松耦合
不仅仅在数据中包容变化还可以在行为中包容变化
用对象处理行为中的变化点
使用被包含对象来提供所需的行为
共同点/变化点以及抽象类
共同点关联于问题领域的概念视角
变化点关联于实现视角
规格视角处在中间
规格描述了如何与一组概念上相似的对象沟通
实现层次上的抽象类和接口
规格视角和概念视角的关系
识别出用来处理这一概念下所有情况的接口
规格视角和实现视角的关系
对于给定的规格,怎样实现这一特定的情况
延伸阅读
The Timeless Way of Building
Object-Oriented Software Construction
A Pattern Language
Multi-Paradigm Design For C++
Cognitive Patterns: Problem-Solving Frameworks for Object Technology
Pattern Hatching
The Greatest Salesman in the World
The Mind Map Book: How to Use Radiant Thinkingto Maximize Your Brain's Untapped Potential
The Ethnographic Interview
Knowledge Management Methods
0 条评论
下一页