Chapter 5: Design in Construction
5.1 Design Challenges
设计是一个险恶的问题
“险恶的”问题是指只有通过解决或者部分解决才能被明确的问题;必须把问题解决一遍以便能够明确的定义它,然后再次解决该问题,从而形成一个可行的方案。软件开发中,只有随着构建活动的推进才能更深刻的理解我们要解决的问题,从而回过头来完善我们的设计。
设计是一个了无章法的问题(即使它能得出清爽的成果)
在设计过程中会采取很多错误的步骤,多次误入歧途。优劣设计之间的差异也往往非常微妙。很难判断设计何时算是“足够好”了、要设计到什么细节、什么时候设计才算完成,最常见的回答是:“到没时间再做设计为止”。
设计就是确定取舍和调整顺序的过程
设计工作者的一个关键内容就是去衡量彼此冲突的各项设计特性,并尽力在其中寻求平衡。
设计受到诸多限制
设计的要点,一部分是在创造可能发生的事情,另一部分又是在限制可能发生的事情。因为有限资源的限制,会促使设计产生简单的方案,并最终改善这一解决方案。
设计是不确定的
计算机程序的设计可能会有数十种不一样的方案,不同的人可能会选择不同的方案。
设计是一个启发式过程
因为设计过程的不确定性,因此设计技术趋于具有探索性:“经验法则”或者“试试没准能行”。设计过程中总有试验和犯错。对于某个项目奏效的设计方案并不一定适用于另一个项目。
设计是自然而然形成的
设计是在不断的设计评估、非正式讨论、写试验代码以及修改试验代码中演化和完善的。软件的性质决定了设计的改变在多大程度上是有益且可被接受的。
5.2 Key Design Concepts
软件的首要技术使命:管理复杂度
偶然的难题和本质的难题
本质的属性是一件事物必须具备、如果不具备就不再是该事物的属性。偶然的属性则是指一件事物碰巧具有的(附属的、任意的、非必要的、偶然出现的)属性。
软件开发的本质就是不断去发掘错综复杂、相互连接的整套概念的所有细节。
本质性的困难来自很多方面:
- 必须面对复杂、无序的现实世界;
- 精确而完整的识别出各种依赖关系和例外情况
- 设计出完全正确而不是大致正确的解决方案
管理复杂度的重要性
当项目由技术因素导致失败时,原因通常是失控的复杂度。管理复杂度是软件开发中最为重要的技术话题。软件开发人员应该尝试以某种方式组织程序以便能够在一个时刻可以专注于一个特定的部分。
软件设计的目标是把复杂问题分解成简单的部分:
- 在软件架构的层次上,可以通过把整个系统分解成多个子系统来降低问题的复杂度
- 精心设计的对象关系使关注点相互分离,更高汇聚层次上,包(packages)提供同样的好处
- 保持子程序的短小精悍也能帮助减少思考的负担;
- 从问题的领域着手,而不是底层实现细节入手去编写程序,在最抽象的层次上工作,也能减少人的脑力负担。
高代价、低效率的设计根源
- 用复杂的方法解决简单的问题
- 用简单但错误的方法解决复杂的问题
- 用不恰当的复杂方法解决复杂问题
如何应对复杂度
- 把任何人在同一时间需要处理的本质复杂度的量减到最少。
- 不要让偶然性的复杂度无谓的快速增长。
理想的设计特征
- 最小的复杂度
- 易于维护
松散耦合
Loose coupling designing so that you hold connections among different parts of a program to a minimum.
可扩展性
可重用性
高扇入
High fan-in refers to having a high number of classes that use a given class.
低扇出
Low-to-medium fan-out means having a given class use a low-to-medium number of other classes.
可移植性
精简性
层次性
Stratification means trying to keep the levels of decomposition stratified so that you can view the system at any single level and get a consistent view.
标准技术
Try to give the whole system a familiar feeling by using standardized, common approaches.
设计的层次

- 软件系统
分解成子系统或者包
确定如何把程序分为主要的子系统,并定义清楚允许子系统如何使用其他子系统,尽量简化子系统之间的交互关系,使得它们简单易懂且易于维护。
常见的子系统
- 业务规则
- 用户界面
- 数据库访问
- 对系统的依赖性
分解为类
- 分解成子程序
- 子程序内部的设计
5.3 Design Building Blocks: Heuristics
找出现实世界中的对象
使用对象进行设计的步骤,这些步骤是迭代、反复执行的:
- 辨识对象及其属性(方法和数据)
- 确定可以对各个对象进行的操作
- 确定各个对象能对其他对象进行的操作
- 确定对象的哪些部分对其他对象可见(public or private)
- 定义每个对象的公开接口
形成一致的抽象
抽象能让你在关注某一概念的同时可以放心的忽略其中的一些细节,能用简化的师徒来看待复杂的概念,从而降低复杂度。
封装实现细节
封装填补了抽象留下的空白,让你看不到复杂概念的任何细节。
恰当使用继承简化设计
抽象能很好的辅佐抽象的概念,简化编程的工作,是面向对象编程中最伟大的工具之一。
隐藏秘密(信息隐藏)
设计一个类的时候,一项关键性的决策就是确定类的哪些特性应该对外可见,哪些应该隐藏起来。类的接口应该尽可能少的保罗起内部工作机制。信息隐藏在设计的所有层次上都有很大作用,从具名常量代替字面常量到创建数据类型,再到类的设计、子程序的设计以及子系统的设计等,隐藏设计决策对于减少“改动所影响的代码量”而言是至关重要的。
信息隐藏中的两类秘密:
- 隐藏复杂度,除非特别关注时,不用去对付它。
- 隐藏变化源,变化发生时,影响能被限制在局部范围内。
信息隐藏的障碍:
- 信息过度分散
- 循环依赖
- 把类内数据误认为是全局数据
- 可以察觉的性能损耗
信息隐藏的价值:
- 信息隐藏有着独特的启发力,它能够激发出有效的设计方案。
- 信息隐藏同样有助于设计类的公开接口
- 设计的所有层面,都可以通过循环该隐藏什么类促成好的设计决策
找出容易改变的区域
好的程序设计所面临的最重要挑战之一就是适应变化,把不稳定的区域隔离出来,从而把变化所带来的影响限制在一个子程序、类或者包的内部。
应对各种变动的措施:
- 找出看起来容易变化的项目
- 把容易变化的项目分离出来
- 把看起来容易变化的项目隔离开来
容易发生变化的区域:
- 业务规则
- 对硬件的依耐性
- 输入和输出
- 非标准的语言特性
- 困难的设计区域和构建区域
- 状态变量
- 数据量的限制
如何更好的使用状态变量:
- 不要使用布尔变量作为状态变量,请换用枚举类型
- 使用访问器子程序取代对状态变量的直接检查
保持松散耦合
耦合度表示类与类之间或者子程序与子程序之间关系的紧密程度。好的耦合关系会松散到恰好能使一个模块能很容易的被另一个模块使用。
衡量模块耦合度的标准:
- 规模:小就是美
- 可见性:两个模块直接连接的显著程度
- 灵活性:模块之间的连接是否容易改动
耦合的种类:
- 简单的数据参数耦合
- 简单的对象耦合
- 对象参数耦合
- 语义上的耦合
查阅常用的设计模式
设计模式提供的益处:
- 通过提供现成的抽象来减少复杂度
- 通过把常见的解决方案的细节予以制度化来减少出错
- 通过提供多种设计方案而带来启发性的价值
- 通过把设计对话替身到一个更高的层次来简化交流
常见设计模式:
- 抽象工厂
- 适配器
- 桥接
- 组合
- 装饰器
- 外观
- Factory Method
- 观察者
- 单件
- 策略
- 模板方法
设计模式的潜在陷阱:
- 强迫让代码适用于某个模式
- 为了模式而模式
其他启发式方法
一些不太常用但值得一提的启发式方法:
- 高内聚性
- 构造分层结构
- 严格描述类契约
- 分配职责
- 为测试而设计
- 避免失误
- 有意识的选择绑定时间
- 创建中央控制点
- 考虑使用蛮力突破
- 画一个图
- 保持设计的模块化