敏捷软件开发学习笔记
邱志军 于2008年初
赛尔软件有限公司(筹)
敏捷开发(AD)是一种面临快速变化的需求快速软件开发的能力。获取敏捷性的三要素:
1. 提供必要的纪律和反馈的实践
2. 保持软件灵活、可维护的设计原则
3. 针对特定问题平衡设计原则的设计模式
人与人之间的交互是复杂的,并且其效果从来都难以预期,但却是工作中最为重要的方面。原则、模式、实践都很重要,但是使它们发挥作用的是人。人不是“插入即兼容的编程装置”,必须构建起有合作精神、自组织的团队。
失控的过程膨胀源于对项目失败的恐惧,在这种恐惧之下过程变得越来越庞大!!!
2001年敏捷联盟发布敏捷联盟宣言,声明其价值观和原则。宣言的具体内容如下:
1. 人是获得成功的最为重要的因素
2. 合作、沟通以及交互能力远比单纯的编程能力更为重要
3. 使用过多庞大、笨重的工具就像缺少工具一样,都是不好的
4. 团队的构建比环境的构建重要的多
1. 过多的文档比更少的文档更糟
2. 编写并维护一份系统原理和结构方面的文档
3. 给新的团队成员传递知,最好的两份文档是代码和团队
4. 直到迫切需要并意义重大时,才编制文档
1. 成功的项目需要有序、频繁的客户反馈
2. 指明了需求、进度以及项目成本的合同存在根本的缺陷
1. 响应变化的能力常常决定一个软件项目的成败
2. 计划不能考虑的太远
3. 较好的计划策略:为下两周做详细的计划,为下三个月做粗略的计划,再以后就做极为粗糙的计划
从上述价值观中引出下面12条原则,它们是敏捷实践区别于重型过程的特征所在。
1. 初期交付的系统功能越少,最终交付的系统的质量就越高
2. 敏捷实践会尽早的、经常的进行交互
1. 敏捷过程利用变化来为客户创造竞争优势
2. 敏捷团队会非常努力保持软件结构的灵活性,以应对需求的变化
1. 不赞成交付大量的文档或计划
2. 我们关注的目标是交付满足客户需求的软件
为了以敏捷的方式进行项目的开发,客户、开发人员和相关涉众之间必须进行有意义的、频繁的交互。
1. 给他们提供所需的环境和支持,并且信任他们能够完成工作
2. 在敏捷项目中,人被认为是项目取得成功的最重要的因素
1. 敏捷项目中,首要的沟通方式就是交谈
2. 敏捷团队不需要书面的规范、计划或者设计
1. 敏捷项目通过度量当前软件满足客户需求的数量来度量开发进度
2. 只有当30%的必修功能完成时,才可以确定进度完成了30%
1. 责任人、开发者和用户应该能够保持一个长期、恒定的开发速度
2. 敏捷项目不是50米短跑,而是马拉松长跑
3. 跑得过快会导致团队精力耗尽,出现短期行为以致崩溃
1. 高的产品质量是获取高的开发速度的关键
2. 保持软件尽可能的简洁、健壮是快速软件开发的途径
1. 敏捷团队不会构建华而不实的系统,更愿意采用和目标一致的最简单的方法
2. 在今天以最高的质量完成最简单的工作
1. 敏捷团队是自组织的团队
2. 敏捷团队的成员共同来解决项目中所有方面的问题
1. 敏捷团队会不断的对团队组织方式、规则、规范、关系等进行调整
2. 为保持团队的敏捷性,团队必须随时间一起变化
敏捷软件开发的原则和价值观构成了一个可以帮助团队打破过程膨胀循环的方法,这个方法关注的是可以达到团队目标的一些简单技术。已经有许多敏捷过程可供选择,包括:SCRUM、FDD、ADP以及XP。
作为开发人员,我们应该记住,极限编程并非唯一选择。
极限编程是敏捷方法中最著名的一个,由一系列简单却相互依赖的实践组成。
1. 客户和开发人员一起紧密的工作,彼此了解对方面临的问题,并共同解决这些问题
2. XP团队中的客户是指定义产品特性并排列这些特性优先级的个人或团体
3. 最好情况是客户和开发人员在一起工作
1. 进行项目计划必修知道项目需求有关的内容,但无需知道太多,做到能够估算即可
2. 需求的特定细节会随着时间而改变
3. 在XP中,我们和客户反复沟通以获取对需求细节的理解,但不捕获那些细节
4. 用户素材是需求谈话的助记符,可以根据它的优先级和估算代价安排时间
XP每两周交付一次可以工作的软件,实现涉众的一些需求。
1. 迭代计划是一次较小的交付,可能会被加入产品中,也可能不会
2. 由客户依据开发人员的预算而选择的用户素材组成
3. 开发人员依据以前迭代完成的工作量为本次迭代设定预算
4. 一旦迭代开始,客户就同意不修改用户素材的定义和优先级
1. 发布计划是一次较大的交付,通常会被加入产品中
2. 由客户依据开发人员的预算而选择的、排好优先级的用户素材组成
3. 开发人员依据以前发布完成的工作量为本次迭代设定预算
4. 发布计划不是一成不变的,用户随时可以改变计划的内容
1. 可以用客户指定的验收测试的测试的形式捕获用户素材的细节
2. 验收测试用能够使他们自动、反复运行的脚本语言编写
3. 验收测试验证系统按照用户指定的行为运转
4. 一旦一项测试通过则加入验收测试集,并绝不允许该测试再次失败
1. 所有的产品代码都是有结对的程序员使用同一台计算机共同完成
2. 两人频繁互换角色
3. 结对关系每天至少改变一次
4. 结对编程催进知识在团队中的传播
5. 结对非但不会降低团队效率,而且会大大减少缺陷率
1. 编写所有产品代码的目的是使失败的单元测试通过
2. 编写测试用例和代码之间的更迭速度是很快的,基本上几分钟左右
3. 测试用例集和代码一起发展起来
4. 为使测试用例通过而编写代码时,编出的代码是可测试的、松耦合的代码
1. 结对编程中的每一对都具有签出任何模块,并对其修改的权利
2. 集体所有权并不意味着不需要专业知识
1. XP团队使用非阻塞式的源代码控制工具
2. 结对人员会在一项任务上工作1-2小时
1. 软件项目不是全速的短跑,而是马拉松长跑
2. XP的规则是不允许团队加班工作,版本发布前一个星期除外
1. 在充满积极讨论的屋子里工作,生产效率非但不会降低,反而会成倍的提高
2. 团队在一个开发的环境中一起工作
1. 计划游戏的本质是划分业务人员和开发人员的职责
2. 业务人员决定特性的重要性
3. 开发人员决定实现一个特性所花费的代价
1. XP团队使他们的设计尽可能简单、具有表现力
2. 仅仅关注本次迭代中的用户素材
3. 以尽可能简单的方式实现用户素材,迫切需要基础结构时才引入基础结构
1. 尽可能寻早能够实现当前用户素材的最简单的设计
2. 能够用平面文件,就不用数据库
3. 能够用Socket就不用ORB或RMI
4. 能够不用多线程就不用多线程
只有在有证据,或有明显的迹象表明引入基础结构比继续等待合算时,才引入基础结构
1. 极限编程者不能容忍冗余的代码
2. 消除重复最好的方法就是抽象
1. 代码往往会腐化
2. XP团队通过经常性的重构来扭转这种退化
3. 重构之后要测试通过
4. 重构是持续进行的,持续保持干净、简单并具有表现力的代码
1. 隐喻是将系统联系在一起的全局视图
2. 隐喻通常可以归纳成一个名字系统
极限编程是一组简单、具体的实践,这些实践组合在一起形成一个敏捷开发过程。
当你能够度量你所说的,并且能够用数字去表达它时,就表示你了解了它;若你不能度量它,不能用数字去表达它,那么说明你的知识是匮乏的,不能令人满意。
1. 项目开始时,开发人员和客户会尽量确定出所有真正重要的用户素材
2. 开发人员共同对这些素材进行估算,估算出相对的“点数”
1. 过大或过小的素材都是难以估算的
2. 当分割或合并一个素材时,应该对其重新进行估算
3. 估算没有给出用户素材的绝对大小,需要引入一个称为速度的因子
4. 随着项目进展,对速度的度量会越来越准确
5. 项目开始时,通过称为探究的原型化过程了解速度
1. 如果知道了开发速度,客户对每个素材的成本就能够有所了解
2. 客户也知道每个素材的商业价值和优先级别
3. 让业务人员来选择那些会给他们带来最大利益的素材
4. 开发人员同客户对项目的首次发布时间达成一致,通常在2-4个月后
5. 当速度变得更准确时,可以再对发布计划进行调整
1. 开发人员和客户共同决定迭代规模,一般为两周
2. 迭代期用户素材的实现顺序属于技术决策范畴
3. 一旦迭代开始,用户就不能改变迭代期内需实现的素材
4. 即使没有完成所有的用户素材,也应该在计划指定日期结束迭代
5. 为迭代做计划采用的速度,就是前一次迭代测算出的速度
6. 这样的速度反馈有利于保持计划同实际状况同步
1. 开发人员把素材分解为开发任务
2. 开发人员在客户的帮助下,尽可能列出所有的任务
3. 一个任务就是开发人员能够在4-16小时内实现的一些功能
4. 可以在活动挂图、白板等方便的媒介上列出任务
5. 开发人员可以签订任意类型的任务
6. 开发人员以上次迭代完成的任务点数签订本次的任务点数
7. 任务的选择、分配过程是一个动态调整的过程
1. 当迭代进行到一半时,会召开一次会议
2. 在此刻,半数的素材点数应该完成,否则重新分配任务和职责
1. 每两周本次迭代结束,下次迭代开始
2. 每次迭代结束,给客户演示可以运行的程序,收集反馈
3. 客户为下次迭代提供新的素材
4. 客户可以经常看到项目的进展,度量开发速度
1. 通过一次次的迭代、发布,项目进入一种可以预测的、舒适的开发节奏
2. 开发人员基于自己的估算,自己度量开发速度,制定合理的计划
3. 管理人员从每次迭代中获取数据,控制和管理项目
编写单元测试是一种验证行为,更是一种设计行为,更是一种编写文档的行为。此外,编写单元测试避免了相当数量的反馈循环,尤其是功能验证方面的反馈循环。
如果我们能够在设计程序前事先设计测试方案,情况会怎样?
如果我们能够做到:除非缺乏某个功能将导致测试失败,否则拒绝在程序中实现该功能,情况会怎么样?
如果我们能够做到:除非缺乏某行代码将导致测试失败,否则拒绝在程序中增加哪怕一行代码,情况会怎么样?
如果首先编写失败的测试以表明需要某项功能,然后再逐渐的增加哪项功能使测试通过,情况会怎么样?
测试驱动的开发方法带来的好处有:
l 程序中的每一项功能都有测试来验证它操作的正确性
l 首先编写测试,可以迫使我们使用不同的观查点
l 首先编写测试,可以迫使我们把程序编写成可测试的
l 首先编写的测试,可以作为一种无价的文档形式
按照便于我们阅读的方式编写测试,然后按照测试所暗示的结构去编写程序,这称为有意图的编程。在实现之前,现在测试中陈述你的意图,使你的意图尽可能简单、易读。
1. 最初的测试指出了一个好的解决问题的方法
2. 测试在非常早的阶段,阐明了一个重要的设计问题
3. 首先编写测试的行为就是在各种设计决策中辨别的行为
在编写产品代码之前,先编写测试常常会暴露出程序中应该被解藕的区域。对类进行单元测试时,往往要同时测试与其协作的类,这时就需要隔离其协作类进行单独的测试。可以通过MOCK OBJECT模拟对象模式,在类及其协作者间插入接口,并创建实现这些接口的测试桩(TEST STUB)。
1. 通过插入接口,我们获得了实现类的互换能力
2. 这种互换即是为了测试,也是为了应用的扩展性
3. 在编写代码前编写测试改善了设计
作为验证工具来说,单元测试是必须的,但不够充分。单元测试用来验证系统的小的组成单元按照所期望的方式工作,但它没有验证系统作为一个整体的正确性。
1. 验收测试由不了解系统内部机制的人编写
2. 验收测试是关于一项特性的最终的文档
3. 编写验收测试的行为对系统的构架方面具有深远的影响
我们应该以我们认为验收测试应该的样子去编写验收测试,然后构造脚本语言,并根据脚本语言的结构来构造系统。
1. 测试套件运行得越简单,就会越频繁的运行它们
2. 单元测试、验收测试都是一种文档形式,而且是可以编译执行的
3. 测试最重要的好处就是它对构架和设计的影响
大千世界中,唯一缺乏的就是人类的注意力。
Martin Fowler将重构定义为:在不改变代码外在行为的前提下对代码做出修改,以改进代码内部结构的过程。为什么要改变软件模块的内部结构呢?这要从软件模块的三项职责说起:
1. 第一个职责是它运行起来所完成的功能
2. 第二个职责是它要应对变化
3. 第三个职责是要和阅读它的人进行沟通
程序变得更易理解,因此也更易更改,且程序结构的各部分之间相互隔离,这也使它更易更改。重构就好比用餐后对厨房的清洁工作,重构的目的是每天清洁你的代码。
设计和编程都是人的活动,忘记这一点,将会失去一切。
在敏捷团队中,全局视图和软件一起演化。在每次迭代时,团队改进系统设计,使设计尽可能适合于当前系统。
l 僵化性(Regidity):设计难以改变
l 脆弱性(Fragility):设计易于遭到破坏
l 牢固性(Immobility):设计难以重用
l 粘滞性(Viscosity):难以做正确的事
l 不必要的复杂性(Needless Complexity):过分设计
l 不必要的重复(Needless Repetition):滥用鼠标
l 晦涩性(Opacity):混乱的表达
面向对象设计的基本原则:
l 单一职责原则(SRP)
l 开放——封闭原则
l Liskov替换原则
l 依赖倒置原则
l 接口隔离原则
设计中的臭味是一种症状,是可以主观进行度量的。这些臭味常常是出于违反了这些原则中的一个或多个而导致的,敏捷团队应用这些原则去除异味。
在按照我的理解方式审查了软件开发的生命周期后,我得出一个结论:实际上满足工程设计标准的唯一软件文档,就是源代码清单。
———Jack Reeves
刚开始时,你能够获得系统的清晰视图,接着事情开始变遭,软件像一片坏面包一样开始腐化。最后,即使仅仅进行最简单的更改,也需要花费巨大的努力。管理人员开始强烈的要求进行重新设计,然而这样的重新设计很少成功。
当软件出现下面任何一种气味时,就表明软件正在腐化。
l 僵化性(Regidity):很难对系统进行改动,因为每个改动都会迫使对系统其他部分的其他改动
l 脆弱性(Fragility):对系统的改动会导致系统中和改动的地方在概念上无关的许多地方出现问题
l 牢固性(Immobility):很难解开系统的纠结,使之成为一些可以在其他系统中重用的组件
l 粘滞性(Viscosity):做正确的事情要比做错误的事情困难
l 不必要的复杂性(Needless Complexity):设计中包含不具有直接好处的基础结构
l 不必要的重复(Needless Repetition):设计中包含重复的结构,而该重复的结构本可以使用单一的抽象进行统一
l 晦涩性(Opacity):很难阅读、理解,没有很好的表现出意图。
在非敏捷的环境中,由于需求没有按照初始设计预见的方式进行变化,从而导致设计的退化。然而,我们不能因为设计的退化而责怪需求的变化。我们必须设法找到一种方法,使得设计对于这种变化就有弹性,并且应用一些实践来防止设计腐化。
敏捷团队依靠变化获取活力。他们更愿意保持系统设计尽可能的简单、干净,并使用单元测试、验收测试做为支援。
敏捷开发人员知道要做什么,是因为:
1) 他们遵循实践去发现问题
2) 他们应用设计原则去诊断问题
3) 他们应用适当的设计模式去解决问题
软件开发的这三个方面的相互作用就是设计。
敏捷开发人员致力于保持设计尽可能的适当、干净。他们从来不说“稍后我们会回来修正他们”。设计必须要保持干净、简单,并且由于源代码是设计最重要的表示,所以它同样要保持干净。
敏捷设计是一个过程,不是一个事件,它是一个持续的应用原则、模式以及实践来改进软件的结构和可读性的过程。它致力于保持系统设计在任何时候都尽可能简单、干净以及富有表现力。
只有佛自己应当担负起公布玄妙秘密的职责。
——E Brewer
单一职责原则,最初也被称为内聚性——一个模块的组成元素之间的功能相关性。
就一个类而言,应该仅有一个引起它变化的原因。每一个职责都是变化的一个轴线,当需求变化时,该变化会反映为类的职责的变化。如果一个类承担的职责过多,就等于把这些职责耦合在一起,这种耦合会导致脆弱的设计。通常对于单一职责的违反可能会导致一些严重的问题。
在SRP中,我们把职责定义为变化的原因,如果你能够想到多于一个的动机去改变一个类,那么这个类就有了多于一个的职责。
常常会有一些和硬件或者操作系统有关的原因,迫使我们把不愿耦合的东西耦合在一起。然而,对于其他部分来说,通过分离他们的接口我们已经解耦了概念。
SRP是所以原则中最简单的,也是最难以正确应用的,实际上其余原则都是以这样或那样的方式回答SRP要解决的问题。
任何系统在其生命周期中都会发生变化,如果我们期望开发出的系统不会在第一版后就抛弃,尤其需要注意这一点。
软件实体应该是可以扩展的,但是不可以修改。如果程序中的一处改动就会产生连锁反应,那么设计就有僵化性的臭味,依旧OCP我们应当对代码进行重构。
遵循开闭原则设计的模块具有以下两种特征:
1. 对于扩展是开放的:这意味着模块的行为是可以扩展的
2. 对于更改是封闭的:对模块扩展时,不必改变模块的源代码或二进制代码。
模块可以操作一个抽象体,由于模块依赖一个固定的抽象体,所以它对于更改可以是封闭的,通过从这个抽象类派生,也可以扩展此模块的行为。
如果我们预测到这种抽变化,就可以设计一个抽象来隔离它。但是,这导致了一个麻烦的结果,一般而言,无论模块设计的多么封闭,都存在一些无法对之封闭的变化。这样我们就得预测变化的发生,并且一直等到变化发生时才采取行动。
过去我们常常通过放置吊钩的方式预先隔离变化,然而放置吊钩常常是错误的。我们不希望背着许多不需要的抽象,直到真正需要时才放置进去。
l 只受一次愚弄:我们最初编写代码时,假设变化不会发生,当变化发生时,我们就创建抽象来隔离以后发生的同类变化
l 刺激变化:我们希望在开放后不久就知道可能发生的变化,因此我们需要去刺激变化
在许多方面,OCP都是面向对象设计的核心所在,遵循这个设计可以带来面向对象技术所声称的诸多好处。
OCP背后的主要机制是抽象和多态,在JAVA中这主要是通过继承来实现。但是,是什么设计规则支配着这种特殊继承用法呢?
——Liskov替换原则
对LSP可以做如下解释:子类型必须能够替换掉他们的基类型。
对于LSP的违反常常会导致以明显违反OCP的方式使用RTTI。
我们经常说继承是一个IS -A关系,这种关系有时会导致一些问题。如果新创建的派生类会导致改变基类,这常常意味着设计存在缺陷。
新创建的子类违反了父类的不变性,进而违反了LSP原则。
LSP让我们得出一个非常重要的结论:一个模型,如果孤立的看,并不具有真正意义上的有效性。模型的有效性只能通过它的客户程序来体现,客户程序往往对模块作出一定的合理假设,有效性建立在对这种假设的预测之上。
从行为方式的角度来看,对象的行为方式才是软件真正所关注的问题。LSP清楚的指明,OOD中IS_A关系是关于行为的,行为方式是可以合理假设的,是客户程序所依赖的。
怎样才能知道客户的真正需求呢?有一项技术可以使这些合理的需求明确化——DBC基于契约的设计。使用DBC,类的编写者显式的规定对该类的契约,契约通过为每个方法声明前置条件和后置条件指定。按照Mayer所述,派生类的前置条件和后置条件规则是:在重写基类的方法时,只能使用相等或更弱的前置条件,只能使用相等或更强的后置条件。
OCP是OOD中许多说法的核心,LSP是是OCP成为可能的主要原则之一。
绝不能再让国家的重大利益依赖于那些会动摇人类薄弱意志的众多可能性。
——Thomas Noon
l 高层模块不应该依赖底层模块,两者都应该依赖于抽象
l 抽象不应该依赖于细节,细节应该依赖于抽象
倒置,倒置的到底是什么呢?许多传统的软件开发方法,如面向结构的分析和设计,总是倾向于创建一些高层模块依赖底层模块、策略依赖于细节的软件结构。而一个面向对象的程序,其软件结构的依赖关系显然是倒置了。
高层模块包含了一个应用程序中重要的策略选择和业务模型,无论如何高层模块都不应该依赖底层模块,否则底层模块的改动势必影响高层模块。我们更希望重用的是高层的策略设置模块,DIP原则是框架设计的核心原则。
Booch曾经说过:所有结构良好的面向对象构架都具有清晰的层次定义,每个层都通过一个定义良好的、受控的接口向外提供一组内聚的服务。然而,对这句话理解不当的话,很容易就做出违反DIP原则的设计。其实,更为合理的设计应该是:每个较高层次都为它所需要的服务声明一个抽象接口,较低的层次实现了这些接口,每个高层类都通过抽象接口使用下一层,这样高层模块就不依赖底层模块。这样,倒置不仅是依赖关系的倒置,也是接口所有权的倒置,当应用DIP原则时,我们发现往往是他们的客户拥有抽象接口,而他们的服务者则从这些抽象接口派生。
底层模块实现了在高层模块中声明的并被高层模块调用的抽象接口——这就是Hollywood原则。通过这种倒置的接口所有权,我们创建了一个更灵、更持久、更易改变的结构。
依赖倒置的简单启发式规则:依赖于抽象,该规则建议不要依赖于具体类——也就是说程序中所有依赖关系都应终止于抽象类或接口。依据这个启发式规则,我们可以得出如下结论:
1. 任何变量都不应该持有一个指向具体类的指针或引用
2. 任何类都不应该从具体类派生
3. 任何方法都不应该复写它的任何基类中的已经实现的方法
依赖倒置可以应用在任何存在一个类向另一个类发送消息的地方。违反DIP原则,高层策略自动依赖于底层模块,抽象就自动的依赖于具体细节。
什么是高层策略呢?它是应用背后的抽象,是那些不随具体细节改变而改变的真理,是系统内部的系统——隐喻。通常,遵循DIP原则的设计中的依赖只是名字上的概念,有时抽象的接口没有所有者,在JAVA中这样的接口一般放在单独的一个包中。
使用传统的过程化程序设计所创建的依赖关系结构,策略是依赖于细节的。面向对像的程序设计倒置了这种依赖。依赖倒置原则是实现面向对象技术承诺的好处的基本底层机制,对于创建可重用的框架是必须的。
接口隔离原则用来处理FAT接口的缺点,如果一个类的接口不是内聚的,就表示该类具有胖的接口。客户程序看到的应该是多个具有内聚接口的抽象基类。
在继承层次中解决接口依赖问题,往往会导致基类中的接口变胖——最终违反接口隔离原则。
既然客户程序是分离的,那么接口也应该保持分离。因为,客户程序对他们使用的接口有作用力。
不应该强迫客户依赖于他们不用的方法,否则就容易导致所有客户程序间不必要的耦合。我们希望尽量避免这种耦合,因此我们希望分离接口。
一个对象的客户不是必须通过该对象的接口去访问它,也可以通过委托或对象的基类去访问它。
胖类会导致它们的客户程序之间产生不正常的并且有害的耦合关系。通过把胖类的接口分解为多个特定于客户程序的接口,这就解除了客户程序和他们没有用到的方法间的依赖关系,并使客户程序之间互不依赖。
没有人天生就有命令他人的权利。——Denis
COMMAND模式是最简单、最优雅的模式之一,然而这种简单性具有欺骗性。COMMAND模式仅有带有一个方法的接口组成,该模式横过了一个非常有趣的界限。
通过对命令概念的封装,该模式解除了系统的逻辑互联关系同实际连接的物理设备之间的耦合。
另一个COMMAND模式的常见用法是创建和执行事务操作。
通过创建和执行事务操作,解除了获取用户数据的代码、验证并操作数据的代码以及业务对象本省之间的耦合关系。
通过创建事务对象,我们可以将事务置于一个列表中,以后在进行事务的相关处理,达到时间上解耦的目的。
如果Command派生类知道do方法可以知道它所执行的方法的细节,那么undo方法就可以取消这些操作,并把状态恢复到以前的状态。记住,这时do方法一般要保留一定的操作状态信息,以供undo方法使用。
活动对象模式是实现多线程控制的一项古老技术,通常通过一个命令链表来实现。
Command模式的简单性掩盖了其对功能性,Command模式可以应用于多种不同的美妙用途,范围设计数据库事务操作,多线程核心,设备控制以及GUI的do/undo管理。
继承关系蕴含的意义是非常深远的,使用继承我们可以基于差异编程。然而,继承非常容易被过度使用,并且过度使用的代价非常高。因此,我们推荐优先使用组合,而不是类继承。
本章介绍了模板方法模式和策略模式,他们解决的问题类似,不过模板方法模式使用继承解决问题,而策略模式使用的则是委托。