用模式的方法来思考
面向模式设计
将注意力集中在高层关联上
从片段开始设计不是一个好的设计方法
想把预先形成的片段累加起来以构造任何拥有自然特征的事物,这是不可能的
将预先成型的部分添加在一起是不能得到优秀的设计的
根据在整体中的位置来定义每个部分
各部分需要与众不同,这样它们可以利用自己独特的环境
在场景中设计片段
每个部分都将根据其在更大的整体环境中的存在而有其特定的形式
从最简单的角度观察问题,然后添加附加的特征(差别)逐渐让设计变得复杂,因为我们加入了更多信息
每个模式都是对空间进行区别的操作即,在以前没有差异的地方创建差异
模式独立于任何人而存在
设计遵循的原则
模式应该按照顺序,一个一个使用
场景优先,首先使用那些为其他模式创造场景的模式
基于模式的设计途径
1. 为了理解需要实现的目标,从对整体的概念性理解开始
2. 识别出在整体中出现的模式
3. 从为其他模式创造场景的模式开始
4. 应用这些模式
5. 对剩下的模式和中途发现的模式,重复步骤3~5
6. 每次一个地应用这些模式以创建场景,在这个场景中对设计进行精炼
标准的设计方法经常让我们只见树木,不见森林,因为我们过分将精力集中于系统的细节上--类
在系统决策过程中,我们经常陷入细节中而忘记了系统最大的场景
细节会成为最大的视图周围的一片阴云,让开发者集中精力于小的,局部的决策
模式给了你一种语言,让你得以超越细节
用模式解决问题
1. 发现我在问题领域中拥有的模式。这些是需要分析的模式
2. 对于需要分析的模式
挑选出为其他模式提供最多场景的模式
在概念性最高的设计中使用这个模式
识别任何可能出现的附加模式
3. 按需将细节添加到设计中,扩展方法和类定义
基本工具
寻找场景
首先关注对象之间的关联
假设在需要的时候可以构造出适合这些关联的对象
推迟考虑如何实例化我需要的对象
尽量减少脑子里考虑的事情的数量
考虑你在系统中需要什么,然后再关心如何实现它
最高级模式约束其他的模式
设计模式的原则和策略
模式在局部和全局范围的作用
模式的机制以及作为它们基础的原则和策略
开放-封闭的原则
对扩展是开放的
对更改是封闭的
将软件设计成这样:在不修改代码的前提下对我们的软件进行扩展
例:Bridge模式中就可以在不修改任何现存的类的前提下加入新的实现部分
从场景进行设计的原则
在设计片段出现的细节之前先创建整体视图
识别可能性不等于必须要跟着可能性走
可能性给了我对问题领域的洞察力
设计模式帮助我看到变化可能在哪里发生,而不是哪个特定的变化将发生
在不知道模式将被怎样使用的前提(即不知道它的场景)下就试图决定实现它,是傻瓜才会作的事情
怎样作出设计决策
通常没有哪个实现方案天生就比另一个更好
在哪种场合下这种选择会比另一种选择更好?
这些场合哪些和我的问题领域最相似?
包容变化的原则
我的继承体系中,类的深度很少超过两层
决不让一个类包含两件变化并以某种方式耦合在一起的事物
模式有效包容变化
模式识别变化之间的关联
通过包容变化,可以接纳未来可能出现的变化
通过适当包容变化,我可以只实现那些我需要的特性,而不必牺牲未来的能力
"试图确定并接纳所有的变化"通常并不能造就好的系统--这种做法根本不能造就系统,这叫作分析瘫痪
终点和起点
面向对象原则的新视角,基于对设计模式的理解
对象是负有定义良好的责任的东西
对象对自己负责
使用共同点/变化点分析抽象出行为和数据中的变化点
针对接口进行设计
把继承考虑为一种封装变化的方法,而不是为现有对象制造特殊情况
把变化点封装在一个类中,并使之与这个类中的其他变化点相分离
力求松耦合,高内聚
绝对细心地应用"一次并且只有一次"规则
设计模式如何帮助我们封装实现
大多数模式提供了隐藏特定实现的途径
隐藏实现的价值
让开发者可以轻易地添加新的实现,因为客户不知道现在的实现是怎样工作的
共同点/变化点分析和设计模式,如何帮助我们理解抽象类
使用共同点/变化点分析来实现很多模式
寻找共同点可以帮助我们发现问题领域中出现的模式
根据涉及的责任对问题领域进行分解
共同点/变化点分析确定了我的概念视角和实现视角
如果只考虑共同点和使用共同点的对象可以从一个不同的角度来考虑问题--按照责任的分解
将问题领域分解为责任
然后定义实现这些责任需要的对象
设计者不应该在知道自己需要的所有对象之前担心如何实例化对象
特定的模式经常会协助我们考虑如何分解责任
确定对象之间的关联
一个模式描述了一个特定场景中特定问题的约束,动机和关联并给了我们一种方法论来讨论这些问题
设计模式和场景化设计
针对接口和多态进行设计
根据场景进行设计
抽象类的接口定义了场景它的所有派生类必须在这个场景内实现
学习模式的途径
这个模式中出现了什么共同点?
这个模式中对象的责任是什么?
这些对象间有什么关联?
这个模式如何成为“根据场景进行设计”的范例?
观察模式的特点
它们封装什么?
它们如何使用共同点/变化点分析?
它们如何按照责任对问题领域进行分解?
它们如何确定对象之间的关联?
它们如何阐述场景化设计
书摘
对象真正的威力不是继承而是“行为封装”
针对接口进行设计
设计模式不是单独存在的,而是需要与其他设计模式协同工作以帮助你构建更健壮的应用程序
顿悟完全改变了我对设计模式的看法:设计模式无法作为独立的条款使用,我应该把设计模式放在一起使用
使用模式来帮助理解乃至描述问题领域,而不是仅仅在理解了问题领域以后使用模式来创建一个设计
基于模式的分析让你成为一个更有力,更高效的分析者。因为它们让你更抽象地处理你的模型,因为它们向你 展示许多其他分析者积累的经验。
避免过早进入实现阶段, 在做之前先想
功能分解是处理复杂问题的一种自然方法。使用功能分解的难题是:它不能帮助我们为未来可能发生的变化做准备,它不能帮助我们的代码优雅的演变
试图同时关注过多的东西,就等于邀请错误与变化同时到来
魔鬼就生活在副作用中。函数的一个重要问题就是可能导致很难发现的副作用
维护和调试的大多数时间不是花在修改错误上,而是花在寻找错误和考虑如何避免在修改中再次引发副作用
使用功能分解时,变化的需求会让我软件开发和维护的成果大受打击。我主要把精力集中在功能上。一个函数和数据结构的变化会影响到其他的函数和数据,于是受影响的部分也要修改。这就像一个滚下山的
面向对象软件设计基础
面向对象范式以前
功能分解
局限性
处理变化
事物总是在变化
用户需求的变化
开发者对需求理解的变化
软件开发环境的变化
分析做得再好,也无法知道用户的所有需求
低内聚,紧耦合
一个函数或数据结构的变化会影响到其他函数和数据
只关注功能,会导致“一处变化引起一连串变化”
传统的面向对象的观点:寻找名词,数据封装,焦点局限于如何实现对象
传统面向对象的局限性
过高的继承体系导致紧耦合,低内聚
过早地对细节投入过多的关心
处理细节总是比较容易
细节上的解决方案很明显
应该尽可能晚地投入到细节中
类爆炸
对继承的过度依赖会导致更高的维护代价
面向对象范式
责任的转移
每个人对自己负责,而不是由控制程序对他们负责
控制程序可以与不同类型的人对话
控制程序不需要知道学生在教室之间移动的具体步骤
使用对象将责任转移到更局部的范围
软件开发过程中的视角 by Martin Fowler
概念
规格
实现
从三个视角层次来考虑问题
在概念层次上通信而在实现层次上执行
请求者不知道具体发生了什么,只知道概念上发生了什么
对象
拥有责任的某种东西
对象应该对自己负责,并且这种责任应该被清晰地定义出来
很多东西不需要暴露给外界
概念层次:一个对象是一系列责任
规格层次:一个对象是一系列可以被其他对象或该对象自己调用的方法
实现层次:一个对象是一堆 代码和数据
类
抽象类
定义了一组类可以做的事情
在概念层次上定义抽象类
封装
向用户隐藏一些东西
好处
使用更容易
实现可以在不考虑调用者的情况下变更
对象外部的代码不知道对象内部的情况
减少副作用
让对象为自己负更多责任,控制程序需要负的责任更少
设计模式
产生于建筑学和人类学
"质量可以客观评价吗?"--Christopher Alexander
对于任何特定的建筑物,优秀的结构之间总有一些相同之处
需要解决的问题的特点中的共同点
结构不可以与它们要解决的问题分离
观察解决相似问题的不同方案
模式:在某一个情境下的问题解决方案
是什么让设计优秀?
是什么让设计拙劣?
模式可以帮助你提升思考的层次
具体模式
Facade
在原始系统的前面构造了一个新的前端接口
适用于
不需要使用原始复杂系统的所有功能
希望包装或隐藏原有系统
编写一个新的类的代价<< 让所有人学会使用该系统的代价
简化接口
Bridge
将抽象部分与实现部分分离,使得它们都可以独立变化
避免抽象部分与实现部分的紧耦合
过度使用继承
由于得到了新的锤子,所有的东西看起来都是钉子
根据对象的责任来考虑它们,而不是根据它们的结构
对象的特点应该基于它们的责任而不是它们包含的东西或它们是什么东西
将注意力集中于模式的场景
模式尝试解决的问题
在确切知道如何实现设计模式之前就判断出何时在问题领域中使用它们
共同点/变化点分析
一种发现对象的新范式比仅仅观察“名词/动词”更好的方法
共同点分析:寻找共同的元素,它们帮助我们理解“家庭成员在哪些方面相同”
元素通过怎样的共同点来定义它们的家族
寻找不太可能随时间变化的结构
变化点分析:“家族成员有什么不同”
只有在一个给定的共同点内,变化点才有意义
捕捉有可能变化的结构
只有在由相关的共同点分析定义的上下文中,变化点分析才有意义
从体系结构角度
共同点分析为体系结构提供了耐久性
变化点分析让它适应各种应用的需要
变化点是问题领域中的具体情况,共同点定义了问题领域中将具体情况捆绑在一起的概念
共同的概念由抽象类实现
变化点分析发现的变化将由具体类来实现
一条规则,一个地方
只在一个地方实现一条规则
如果你的程序中有做某一件事的规则,只实现它一次
一次并且只有一次
系统必须与你希望沟通的任何事物沟通
系统不能包含任何重复的代码
在这个模式里有一个抽象部分(以及派生类)和一个实现部分
设计实现部分的接口时,应该考虑它必须支持的抽象类的不同的派生类
关键特征
意图:将一组实现部分从另一组使用它们的对象中分离出来
问题:一个抽象类的派生类必须使用多种实现部分,但又不能引起类数量的爆炸
解决方案:为所有的实现部分定义一个接口,让抽象类的所有派生类使用这个接口
参与者与协作者
Abstraction为正在实现的对象定义接口
Implementor为特定的实现部分类定义接口
Abstraction的派生类使用Implementor的派生类而不必知道自己使用的特定的ConcreteImplementor
Abstract Factory
意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
跨接分析和设计的策略
发现并封装变化点
优先使用对象组合,而不是类继承
针对接口设计,而不是针对实现设计
延伸阅读
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