敏捷软件开发——原则、模式与实践 Robert C.Martin

本文深入探讨了敏捷开发的基石,如敏捷联盟宣言和极限编程(XP)实践,强调了个体和交互、可工作的软件、客户合作以及响应变化的重要性。介绍了短迭代周期、结对编程、测试驱动开发和重构等方法,旨在通过持续改进保持软件设计的灵活性和高质量。同时,文章阐述了如何通过测试、设计原则和模式来防止软件腐化,确保代码的可维护性和可扩展性。
摘要由CSDN通过智能技术生成

第一章 敏捷实战

敏捷联盟宣言
个体和交互         胜过    过程和工具
可以工作的软件     胜过    面面具到的文档
客户合作           胜过    合同谈判
响应变化           胜过    遵循计划

虽然右项也有价值,但我们认为左项具有更大的价值。
  • 个体和交互胜过过程和工具
    • 人是获得成功的最为重要的因素。如果团队中没有优秀的成员,那么就是使用好的过程也不能从失败中挽救项目,但是不好的过程却可以使最优秀的团队成员失去效用。
    • 一个优秀的团队成员未必就是一个一流的程序员。一个优秀的团队成员可以是一个平均水平的程序员,但是却能够很好地和他人合作。
    • 合适的工具对于成功来说是非常重要的。不要使用过多庞大笨重的工具。
    • 团队的构建要比环境的构建重要的多。应该首先致力于构建团队,然后再让团队基于需要来配置环境
  • 可以工作的软件胜过面面俱到的文档
    • 没有文档的软件是一场灾难。代码不是传达系统原理和结构的理想媒介。团队更需要编写易于阅读的文档,来对系统及其设计决策的依据进行描述。
    • 过多的文档比过少的文档更糟。过多的文档编写花费大量的时间,保持同步和更新将要花更多的时间。不同步,那么就没有价值,造成误导。
    • 维护一份仅论述系统的高层结构和概括的设计原理的文档,具有短小和并且主题突出的特点。
    • 对于新成员,我们采用一起工作来把我们的知识传授给他们。通过近距离的培训和交互使他们成为团队的一部分。
    • 给新团队成员传授知识方面,最好的两份文档是代码和团队。代码真实地表达它所做的事情。团队成员的头脑中,保存着时常变化的系统的脉络图。
    • 直到迫切需要并且意义重大时,才需要编制文档
  • 客户合作胜过合同谈判
    • 成功的项目需要有序、频繁的客户反馈。不是依赖于合同或者工作的陈述,而是让软件的客户和开发团队密切的一起工作,并尽量经常地提供反馈。
  • 响应变化胜过遵循计划
    • 响应变化的能力常常决定着一个项目的成败。当构建计划时,应该确保计划是灵活的并且易于适应商务和技术方面的变化。
    • 计划不能考虑的过远,首先,商务环境很可能变化,这会引起需求变化。其次,客户看见系统开始运作,他们可能会改需求。最后,即使我们确定需求。仍然不能很好的预估时间。
    • 好的策略是:为下两周做详细的计划,为下三月做粗略的计划,再以后就做极为粗糙的计划。
    • 计划中这种逐渐降低的细致度,意味着我们仅仅对于迫切的任务才花费时间进行详细的计划。保持灵活性
原则
  • 我们优先要做的是通过尽早的、持续的交付有价值的软件来使客户满意。
    • 初期交付的系统中所包含的功能越少,最终交付的系统的质量就越高。
    • 交付的越频繁,最终产品的质量就越高。
    • 敏捷实践会尽早地、经常地进行交付。
  • 即使到了开发后期,也欢迎改变需求。敏捷过程利用变化来为客户创造竞争优势
    • 敏捷过程的参与者不惧怕变化。
    • 敏捷团队会非常努力地保持软件结构的灵活性,这样当需求变化时,对于系统造成的影响是最小的。
  • 经常性地交付可以工作的软件,交付间隔可以从几周到几个月,交付的时间间隔越短越好。
    • 我们不赞成交付大量的文档和计划,那些不是真正需要交付的东西。我们关注的目标是交付满足客户需要的软件。
  • 在整个项目的开发期间,业务人员和开发人员必须天天都在一起工作。
  • 围绕被激励起来的个人来构建项目。给他们提供所需要的环境和支持,并且信任他们能够完成工作。
    • 在敏捷项目中,人被认为是项目取得成功的最重要的因素。所有其他的因素–过程、环境、管理等等–都被认为是次要的,并且当它们对人有负面的影响时,就要对他们进行改变。
  • 在团队内部,最具有效果并且富有效率的传递信息的方法,就是面对面交谈。
    • 在敏捷项目中,首要的沟通方式就是交谈。文档可以编写,但不会包含所有的项目信息。文档的需求需要是迫切且意义重大的才编写。敏捷团队不需要书面的规范、书面的计划或者书面的设计。
  • 工作的软件是首要的进度度量标准。
    • 敏捷项目通过度量当前软件满足客户需求的数量来度量开发进度。只有当30%的必须功能可以工作时,才可以确定进度完成30%。
  • 敏捷过程提倡可持续的开发速度。责任人、开发者和用户应该能够保持一个长期的、恒定的开发速度。
    • 敏捷团队会测量他们的速度。他们不允许自己过于疲惫。他们不会借用明天的精力来在今天多完成一点工作。他们工作在一个可以使在整个项目开发期间保持最高质量标准的速度上。
  • 不断关注优秀的技能和好的设计会增强敏捷能力。
    • 高的产品质量是获得高的开发速度的关键,保持软件尽可能的简洁、健壮是快速开发软件的途径。他们不会制造混乱,如果今天制造了混乱,他们会在今天把混乱清理干净。
  • 简单–使未完成的工作最大化的艺术–是根本的。
    • 敏捷团队不会试图去构建那些华而不实的系统,他们总是更愿意采用和目标一致的最简单的方法。他们并不看重对于明天会出现的问题的预测,也不会在今天就对那些问题进行防卫。相反,他们在今天以最高质量完成简单的工作,深信如果明天发生了问题,也会很容易进行处理。
  • 最好的架构、需求和设计出自于有组织的团队
    • 敏捷团队是自组织的团队。任务不是从外部分配给单个团队成员,而是分配给整个团队,然后再由团队来确定完成任务的最好办法。
    • 敏捷团队的成员共同解决项目中所有方面的问题。每一个成员都具有项目中所有方面的参与权利。
  • 每个一段时间,团队会在任何才能更有效地工作方面进行反省,然后相应地对自己的行为进行调整。
    • 敏捷团队会不断对团队的组织方式、规则、规范、关系等进行调整。敏捷团队指导团队所处的环境在不断地变化,并且知道为了保持团队的敏捷性,就必须与环境一起变化。

第二章 极限编程概述(XP)

极限编程实践
  • 客户作为团队成员

    • 我们希望客户和开发人员一起紧密的工作,以便知晓对方所面临的问题,并共同去解决它。
    • 谁是客户?XP团队中的客户是指定义产品的特性并排列这些特性优先级的人或者团体。
  • 用户素材

    • 用户素材就是正在进行的关于需求谈话的助记符。它是一个计划工具,客户可以使用它并根据它的优先级和估算代价来安排实现该需求的时间。
  • 短交付周期

    • XP项目每两周交付一次可工作的软件。迭代结束,给涉及人员演示生成的系统,以得到他们的反馈。
    • 迭代计划:每次迭代为两周。它由客户根据开发人员确定的预算选择一些用户素材组成。
    • 发布计划:XP团队通常会创建一个计划来规划随后6次迭代的内容,这就是所谓的发布计划。
  • 验收测试

    • 验收测试使用能够让它们自动并且反复运行的某种脚本语言编写,这些测试共同来验证系统按照客户指定的行为运转。
  • 结对编程

    • 结对编程将极大的促进知识在团队中的传播。
  • 结对关系需要每天至少改变一次,以便于程序员在一天可以在两个不同的结对中工作。

  • 测试驱动开发的方法

    • 编写所有产品代码的目的都是为了使失败的单元测试能够通过。
    • 编写测试和代码之间的迭代速度是很快的,基本上几分钟一次。
    • 作为结果,一个完整的测试用例集就和代码一起发展起来。
    • 当为了使测试用例通过而编写代码时,这样的代码就被定义为可测试的代码。这样做会强烈地激发你去解除各个模块间的耦合,这样能够独立对它们进行测试。
  • 集体所有权:结对编程的每一对都具有拆出(check out)任何模块并对它进行改进的权利。

  • 持续集成

    • 程序员每天都会多次拆入(check in)他们代码并进行集成。程序员可以在任何时候拆出任何模块,而不管其他人是否已经拆出这个模块。当完成修改并将该模块拆入时,他必须要把他所做的改动和在他之前拆入该模块的程序员所做的任何改动进行合并。为了避免合并的时间过长,团队的成员需要频繁地拆入他们的模块。
    • XP团队每天会进行多次系统构建,他们会重新构建整个系统。
  • 可持续的开发速度

    • 软件开发不是全速的短跑,它是马拉松长跑。
    • XP的规则是不允许团队加班工作。在版本发布前一个星期除外[冲刺阶段]。
  • 开放的工作空间:充满积极讨论的屋子不会降低效率

  • 计划游戏

    • 计划游戏的本质是划分业务人员和开发人员之间的职责。业务人员(客户)决定特性的重要性,开发人员决定实现一个特性所花费的代价。
    • 在每次发布和迭代的开始,开发人员基于最近一次迭代或者最近一次发布中他们所完成的工作量,为客户提供一个预算。客户选择那些所需成本合计起来不超过预算的用户素材。
  • 简单的设计

    • XP团队使他们的设计尽可能简单、具有表现力。此外,他们仅仅关注于计划在本次迭代中要完成的用户素材。他们不会考虑那些未来的用户素材。相反,在一次次的迭代中,他们不断变迁系统设计,使之对正在实现的用户素材而言始终保持在最优状态
    • 考虑能够工作的最简单的事情
    • 除非必须,不要引入新的基础结构
    • 不要重复代码。消除重复最好的方法就是抽象。毕竟,如果两种事务相似的话,必定存在某种抽象能够统一它们。这样,消除重复的行为就会迫使团队提炼出许多的抽象,并进一步减少代码间的耦合。
  • 重构:代码会腐化。采用经常性的重构来扭转这种退化。在每次细微改造之后,通过运行单元测试确保没有造成任何破坏。重构是持续进行的。

  • 隐喻

    • 它是整个系统联系在一起的全局视图;它是系统的未来景象,是它使得所有单独模块的位置和外观变得明显直观。如果模块的外观和整个系统的隐喻不符,那么你就知道该模块是错误的。
    • 隐喻通常可以归结为一个名字系统。这些名字提供了一个系统组成元素的词汇表,并且有助于定义它们之间关系。

第三章 计划 【对极限编程“计划游戏”的描述】

  • 初始探索
    • 在项目开始时,开发人员和客户会尽量去确定出所有真正重要的用户素材。随着项目开发,客户会不断编写新的用户素材。素材的编写会一直持续到项目完成。
    • 开发人员对这些素材进行估算。过大或过小的素材时难以估算的。开发人员往往会低估那些大的素材而高估那些小的素材。过大的分解,过小的合并。
    • 随着项目发展,可以根据已完成的素材点数估算,对于速度的估算会越来越准确。
  • 发布计划
    • 如果知道了开发速度,客户就能够对每个素材的成本有所了解。他们也知道每个素材的商业价值和优先级别。据此,他们可以选择最想最先完成的素材。
    • 客户根据开发速度选择要做的素材。
  • 迭代计划
    • 开发和客户决定迭代规模,一般需两周。客户选择首先要迭代的素材。他们不能选择与当前开发速度不符的更多素材。
    • 迭代期间用户素材的实现由开发人员决定。
    • 一旦迭代开始,客户就不能再改变该迭代周期内需要实现的素材。除了开发人员正在实现的素材外,客户可以任意改变或重新安排项目中的其他任何素材。
    • 即使没有完成所有的用户素材,迭代也要在先前指定的日期结束。合计本次迭代的开发速度,作为下一次迭代的依据。
  • 任务计划
    • 在新的迭代开始时,开发人员和客户共同制定计划。开发人员把素材分解为任务,4~16小时内能实现。开发人员在客户的帮助下对这些素材进行分析,并尽可能完全地列举出所有的任务。
    • 列出任务,在白板或其他媒介。开发人员逐个签订他们想要实现的任务。按自己的开发速度。
    • 迭代的中点,在迭代进行到一半时,团队会召开一次会议。在这个时间点上,本次迭代中所安排的半数素材应该被完成。如果没有完成,团队会设法重新分配没有完成的任务和职责,以保证迭代结束时能够完成所有的素材。如果不能实现重新分派,则需要告诉客户。客户可以决定去掉一个素材或任务。
  • 迭代
    • 每两周,本次迭代结束,下次迭代开始。在每次迭代结束时,会给客户演示当前可运行的程序。要求客户对项目程序的外观、感觉和性能进行评价。客户会以新的用户素材的方式提供反馈。
    • 客户可以经常看到项目的进展,他们可以度量开发速度。他们可以预测团队工作的快慢,并且他们可以在早期安排实现高优先级别的素材。简而言之,他们拥有他们需要的所有数据和控制权,可以按照他们的意愿去管理项目。
  • 结论
    • 通过一次次迭代和开发,项目进入一种可以预测的、舒适的开发节奏。每个人都知道将要做什么,以及何时去做。客户可以接触可以工作的软件,并且还可以提供反馈。
    • 开发人员看到的是基于他们自己的估算并且由他们自己度量的开发速度控制的合理的计划。他们选择他们感觉舒适的任务,并保持高的工作量。
    • 管理人员从每次迭代中获取数据,他们使用这些数据来控制和管理项目。他们不必采用强制、威胁或者恳求开发人员衷心的方式去达到一个武断的、不切实际的目标。
    • 这听起来很美好,但是客户对产生的数据并不总是很满意,特别是刚刚开始时。使用敏捷方法并不意味着客户可以得到他们想要的。它只不过意味着他们能够控制团队以最小代价获得最大的商业价值。

第四章 测试

  • 编写单元测试是一种验证行为,更是一种设计行为。同样,它更是一种编写文档的行为。编写单元测试避免了相当数量的反馈循环,尤其是功能验证方面的反馈循环。
  • 测试驱动开发方法
    • 程序中的每一项功能都有测试来验证它的操作的正确性。这个测试套件给以后的开发提供支援。
    • 编写测试可以迫使我们使用不同的观察点。我们必须从程序调用者的有利视角去观察我们将要编写的程序。
    • 通过首先编写测试,迫使我们把程序设计成可测试的。为了程序易于调用和可测试的,程序就必须和它的周边环境解耦。
    • 测试可以作为一种无价的文档形式。
  • 测试促进代码之间的隔离
    • 在编写代码之前,先编写测试常常会暴露程序被耦合的区域。
  • 验收测试
    • 单元测试是用来验证系统中个别机制的白盒测试。验收测试是用来验证系统满足客户需求的黑盒测试。
    • 验收测试由不了解系统内部机制的人编写。客户可以直接或者和一些技术人员一起来编写验收测试。验收测试时程序,因此是可以运行的。然而,通常采用专为应用程序的客户创建的脚本语言来编写验收测试。
  • 结论
    • 测试套件运行起来越简单,就会越频繁地运行它们。测试运行的越多,就会越快地发现和那些测试的任何背离。如果一天能多次运行所有的测试,那么系统失效的时间就绝不会超过几分钟。我们不允许系统倒退。一旦它工作在一个确定的级别上,就绝不能让它倒退到一个较低饿的级别。
    • 单元测试和验收测试都是一种文档形式,那样的文档是可以编译和执行的;因此,它是准确和可靠的。程序员能够阅读单元测试,因为单元测试是使用程序员编程的语言编写的。客户能够阅读验收测试,因为验收测试是使用客户自己设计的语言编写的。
    • 也许,测试最重要的好处就是它对于架构和设计的影响。为了使一个模块或者应用程序具有可测试性,必须要对它进行解耦合。越是具有可测试性,耦合关系就越弱。全面考虑验收测试和单元测试的行为对于软件的结构具有深远的正面影响。

第五章 重构

  • 重构(Refactoring):在不改变代码外在行为的前提下对代码作出修改,以改进代码的内部结构的过程。
  • 软件模块的三项职责
    • 它运行起来所完成的功能。
    • 要应对变化。几乎所有的模块在它们的生命周期中都要变化,开发者有责任保证这种改变应该尽可能的简单。一个难以改变的模块是拙劣的,需要修正它。
    • 要和阅读他的人进行沟通。对该模块不熟悉的开发人员应该能够比较容易阅读它。一个无法沟通的模块也是拙劣的,需要修正它。
  • 重构的目的,是为了每天清洁你的代码

第六章 一次编程实践

第七章 什么是敏捷开发

  • 在敏捷团队中,全局视图和软件一起演化。在每次迭代中,团队改进系统设计,使设计尽可能适合于当前系统。团队不会花费许多时间去预测未来的需求和需要,也不会试图在今天就构件一些基础结构去支撑那些他们明天才需要的特性。他们更愿意关注当前的系统结构,并使它尽可能地好。
  • 软件系统的源代码是它的主要设计文档
  • 设计的臭味–腐化软件的气味
    • 僵化性:很难对系统进行修改,因为每一个改动都会迫使许多系统其他部分的其他改动。
      • 单一的改动会导致有依赖关系的模块中的连锁改动,那么设计就是僵化的。必须要改动的模块越多,设计就越僵化。
    • 脆弱性: 对系统的改动会导致系统中和改动的地方在概念上无关的许多地方出现问题。
      • 随着模块脆弱性的增加,改动会引出意想不到的问题的可能性就越来越大。
    • 牢固性:很难解开系统的纠结,使之成为一些可在其他系统中重用的组件。
    • 粘滞性:做正确的事比做错误的事情更难。
      • 软件的粘滞性:当面领一个改动时,开发人员常常发现会有多种改动的方法。其中一些方法会保持设计,另外一些会破坏设计(生硬的手法)。当那些可以保持系统设计的方法比那些生硬手法更难应用时,就表现设计具有高的粘滞性。我们希望在软件开发中,可以容易地保持设计的变动。
      • 当开发环境迟钝、低效时,就会产生环境的粘滞性。例如,如果编译所花费的时间很长,那么开发人员就会引诱去做不会导致大规模重编译的改动,即使那些改动不再保持设计。如果源代码控制系统需要几个小时拆入(check in)几个文件,那么开发人员就会被引诱去做那些需要尽可能少拆入的改动,而不管改动是否保持设计。
      • 无论项目具有哪种粘滞性,都很难保持项目中的软件设计。我们希望创建易于保持设计的系统和项目环境。
    • 不必要的复杂性:设计中包含有不具任何直接好处的基础结构。
      • 当开发人员预测需求的变化,并在软件中放置了处理那些潜在变化的代码时,常常会出现这种情况。但是,为过多的可能性做准备,致使设计中含有绝对不会用到的结构,从而变得混乱。一些可能会带来回报,但是更多的不会。期间,设计背负着这些不会用到部分,使软件变得复杂,并且难以理解。
    • 不必要的重复:设计中包含有重复的结构,而该重复的结构本可以使用单一的抽象进行统一。
      • 剪切和粘贴也许是有用的文本编辑操作,但是它们却是灾难性的代码编辑操作。时常,软件系统都是构建在众多的重复代码片段之上。
      • 当同样的代码以稍微不同的形式一再出现时,就表示开发人员忽略了抽象。对于他们来说,发现所有重复并适当的抽象去消除它们的做法可能没有高的优先级别,但是这样做非常有助于使系统更加易于理解和维护。
      • 当系统存在重复代码时,修改会变得困难。在一个重复代码体中的错误必须在每一个代码体中一一修正。不过,由于每一个重复代码之间都有细微差别,所以修正的方式也不尽相同。
    • 晦涩性:很难阅读、理解。没有很好的表达意图。
      • 代码随着时间的演化,往往会变得越来越晦涩。为了使代码的晦涩性保持最低,就需要持续低保持代码清晰并富有表现力。
      • 开发人员在编写一个模块时,代码对于他们来说也许是清晰的。这是由于他们专注于代码的编写,并且他们对代码非常的熟悉。但是在熟悉减退之后,他们会觉得这是一段糟糕的代码。为了防止此类情况,开发人员必须要站在代码阅读者的位置,共同努力对代码进行重构,这样代码的阅读者就可以理解代码。
  • 什么激化了软件的腐化
    • 在非敏捷环境中,由于需求没有按照初始设计预见的方式进行变化,从而导致了设计的退化。
    • 软件的不断改变,偏离原始设计的不断积累,设计出现了臭味。
  • 敏捷团队不允许软件腐化
    • 敏捷团队依靠变化来获取活力。团队几乎不预先设计,因此,不需要一个成熟的初始设计。他们更愿意保持系统设计尽可能的干净、简单,并使用许多单元测试和验收测试作为支援。这保持了设计的灵活性、易于理解性。团队利用这种灵活性,持续的改进设计,以便于每次迭代结束所生成的系统都具有最适合于那次迭代中需求的设计。
  • 需求在变化: 软件开发最重要的事实之一:需求总在变化。
    • 大多数软件项目中最不稳定的东西就是需求。需求处在一个持续变动的状态之中。这是我们必须接受的事实!我们生存在需求不断变化的世界中,我们的工作是要保证我们的软件能够经受得住那些变化。如果我们软件的设计由于需求变化了而退化。那么我们就不是敏捷的
  • 敏捷设计:开始以最简单的方式编写代码。直到需求最终确实变化时,修改之前的设计,使之对该变化保持弹性。
  • 敏捷人员知道要做什么、
    • 他们遵循敏捷实践去发现问题;
    • 他们应用设计原则去诊断问题;
    • 并且他们应用适当的设计模式去解决问题。
    • 软件开发的上述三个方面之间的相互作用就是设计。
  • 保持尽可能好的设计
    • 敏捷开发人员致力于保持设计尽可能地适当、干净。他们不是每几周才清理他们的设计。而是每天、每小时、甚至每分钟都要保持软件尽可能地干净、简单并富有表现力。
    • 设计必须保持干净、简单,并且由于源代码是设计最重要的表示,所以它同样要保持干净。职业特性告诉我们,作为软件开发人员,不能忍受代码腐化。
  • 结论
    • 敏捷设计是一个过程,不是事件。它是一个持续的应用原则、模式以及实践来改进软件的结构和可读性的过程。它致力于保持系统设计在任何时候都尽可能地简单、干净以及富有表现力。
    • 敏捷开发人员不会对一个庞大的预先设计应用软件的原则和模式(SOLID)。相反,这些原则和模式都应用在一次次的迭代中,力图使代码以及代码所表达的设计保持干净。

第八章 单一职责原则SRP

  • 内聚性:一个模块组成元素之间的功能相关性
  • 单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。
  • 如果一个类承担的职责过多,就等于把这些职责耦合在一起。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。
  • 在SRP中,我们把职责定义为“变化的原因”。
  • SRP是所有原则中最简单的之一,也是最难正确运用的之一。我们会自然地把职责结合在一起。软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。

第九章 开放-封闭原则OCP

  • OCP:软件实体(类、模块、函数等等)应该是可以扩展的,但是不可修改的。
  • OCP的特征
    • **对于扩展时开放的。**这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。换句话说,我们可以改变模块的功能
    • **对于更改是封闭的。**对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者Java的.jar文件,都无需改动。
  • 注重抽象
  • 隔离变化
  • 为了防止软件背着不必要的复杂性,我们允许自己被愚弄一次。这意味着在我们最初编写代码时,假设变化不会发生。当变化发生时,我们就创建抽象来隔离以后发生的同类变化。
  • 刺激变化:我们希望在开发工作展开后不久就知道可能发生的变化。查明可能发生的变化所等待的时间越长,要创建正确的抽象就越困难。
    • 首先编写测试。
    • 短迭代周期开发
    • 先开发最重要的特性
    • 尽早、经常性地发布软件。

第十章 Liskov替换原则 LSP

  • LSP:子类型(subtype)必须能够替换掉它们的基类型(base type)。
  • 对于LSP的违反常常会导致以明显违反OCP的方式使用运行时类型辨别。这种方式常常是使用一个显式的if语句或者if/else链去确定一个对象的类型,以便可以选择针对该类型的正确行为。
  • LSP是使OCP成为可能的主要原则之一。正是子类型的可替换性才使得基类类型的模块在无需修改的情况下就可以扩展。这种可替换性必须是开发人员可以隐式依赖的东西。因此,如果没有显式地强制基类类型的契约,那么代码就必须良好地并且明显地表达出这一点。

第十一章 依赖倒置原则DIP

  • 定义:
    • 高层模块不应该依赖于底层模块。二者都应该依赖于抽象
    • 抽象不应该依赖于细节。细节应该依赖于抽象
  • 依赖于抽象:启发式规则
    • 任何变量都不应该持有一个指向具体类的指针或者引用
    • 任何类都不应该从具体类派生
    • 任何方法都不应该覆写它的任何基类中的已经实现了的方法。
  • 结论
    • 使用传统的过程化程序设计所创建出来的依赖关系结构,策略是依赖于细节的。这是糟糕的,因为这样会使策略受到细节改变的影响。面向对象的程序设计倒置了依赖关系结构,使得细节和策略都依赖于抽象,并且常常是客户拥有服务接口。
    • 事实上,这种依赖关系的倒置正是好的面向对象设计的标志所在。使用何种语言来编写程序是无关紧要的。如果程序的依赖关系是倒置的,它就是面向对象的设计。如果程序的依赖关系不是倒置的,它就是过程化的设计。
    • 依赖倒置原则是实现许多面向对象技术所宣称的好处的基本底层机制。它的正确应用对于创建可重用的框架来说是必须的。同时它对于构建在变化面前富有弹性的代码也是非常重要的。由于抽象和细节被彼此分离,所以代码也非常容易维护。

第十二章 接口隔离原则 ISP

  • 定义:不应该强迫客户依赖于它们不用的方法。
  • 使用委托分离接口
  • 使用多重继承分离接口
  • 胖类会导致它们的客户程序之间产生不正常的并且有害的耦合关系。当一个客户程序要求该胖类进行一个改动时,会影响到所有其他的客户程序。因此,客户程序应该仅仅依赖于它们实际调用的方法。通过把胖类的接口分解为多个特定与客户程序的接口,可以实现这个目标。每个特定于客户程序的接口仅仅声明它的特定或者客户组调用的那些函数。接着,该胖类就可以继承所有特定客户程序的接口,并且实现它们。这就解除了客户程序和它们没有调用的方法间的依赖关系,并使客户程序之间互不依赖。

第十三章 COMMAND模式和ACTIVE OBJECT模式

第十四章 TEMPLATE METOD模式和STRATEGY模式:继承与委托

  • 优先使用对象组合,使用组合或委托来代替类继承。
  • TEMPLATE METHOD模式使用继承来解决问题,而STRATEGY模式使用委托。它们都可以用来分离高层的算法和低层的具体实现细节。

第十五章 FACADE模式和MEDIATOR模式

  • 它们具有共同的目的,都把某种策略(policy)施加到另一组对象上。FACADE从上面施加策略,MEDIATOR模式从下面施加策略。FACADE模式的使用时明显且受限的,而MEDIATOR模式的使用则是不明显且不受限的
  • MEDIATOR模式(中介者模式):中介者对象封装了一系列的对象交互,中介者使各对象不需要彼此联系来相互作用,从而使耦合松散,而且可以独立的改变他们之间的交互。

第十六章 SINGLETON模式和MONOSTATE模式

  • SINGLETON模式使用私有构造函数,一个静态变量,以及一个静态方法对实例化进行控制和限制。
  • MONOSTATE模式只是简单地把对象的所有变量变成静态的。
  • 如果希望通过派生去约束一个类,并且不介意它的调用者都必须要调用instance()方法来获取访问权,那么SINGLETON是最合适的。如果希望类的单一性本质对使用者透明,或者希望使用单一对象的多态派生对象,那么MONOSTATE是最合适的。

第十七章 NULL OBJECT模式

  • 对于一个接口,NULL OBJECT实现了它的所有方法,但是什么也不做,当获取数据不存在时,返回此类。

第十八章 薪水支付案例研究

第二十章 包的设计原则

  • 通过把类组织成包,我们可以在更高层次的抽象上来理解设计。我们也可以通过包来管理软件的开发和发布。目的就是根据一些原则对应用程序中的类进行划分,然后把那些划分后的类分配到包中。
  • 包的内聚性原则
    • 重用发布等价原则:重用的粒度就是发布的粒度
    • 共用重用原则:一个包中的所有类应该是共同重用的。如果重用了包中的一个类,那么就要重用包中的所有类。
    • 共同封闭原则:包中的所有类对于同一性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对于其他的包不造成任何影响。这条规则规定了一个包不应该包含多个引起变化的原因
  • 稳定性:包的耦合性原则
    • 无环依赖原则:在包的依赖关系图中不允许存在环。
      • 解除环,使用依赖倒置或创建新包把相互依赖的类移到这个包中
    • 稳定依赖原则:朝着稳定的方向进行依赖
    • 稳定抽象原则:包的抽象程度应该和其稳定程度一致。
      • 一个稳定的包应该也是抽象的,这样它的稳定性就不会使其无法扩展。
      • 一个不稳定的包应该是具体的,因为它的不稳定性使得其内部的具体代码易于更改。

第二十一章 FACTORY模式

  • FACTORY模式允许我们只依赖抽象接口就能创建出具体对象的实例

第二十三章 COMPOSITE模式

附录D源代码就是设计

  • 实际的软件运行于计算机之中。它是存储在某种磁介质中的0和1的序列。它不是使用C++或其他编程语言编写的程序。
  • 程序清单是代表软件设计的文档。实际上把软件设计构建出来的是编译器和连接器。
  • 构件实际软件的廉价程度是令人难以置信的,并且它始终随着计算机速度的加快而变得更加廉价。
  • 设计实际软件的昂贵程度是令人难以置信的,之所以如此,是因为软件的复杂性是令人难以置信的,并且软件项目的几乎所有的步骤都是设计过程的一部分。
  • 编程是一种设计活动——好的软件设计过程认可这一点,并且在编程显得有意义时,就会毫不犹豫的去编码。
  • 编码要比我们所认为的更频繁地显现出它的意义。通常,在代码中表现设计的过程会揭示出一些疏漏以及额外的设计需要。这发生的越早,设计就会越好。
  • 因为软件构建起来非常廉价,所以正规的工程验证方法在实际的软件开发中没有多大用处。仅仅构件设计并测试它要比试图去证明它更简单、更廉价。
  • 测试和调试是设计活动——对于软件来说,它们就相当于其他工程学科中的设计验证和改进过程。好的软件设计过程认可这一点,并且不会试图去减少这些步骤。
  • 还有一些其他的设计活动——称它们为高层设计、模块设计、结构设计、架构设计或者诸如此类的东西。好的软件设计过程认可这一点,并且慎重地包含这些步骤。
  • 所有的设计活动都是相互影响的。好的软件设计过程认可这一点,并且当不同的设计步骤显示出有必要时,它会允许设计改变,有时甚至是根本上的改变。
  • 许多不同的软件设计表示法可能是有用的——它们可以作为辅助文档以及工具来帮助简化设计过程。它们不是软件设计。
  • 软件开发仍然还是一门工艺,而不是一个工程学科。主要是因为缺乏验证和改善设计的关键过程中所需的严格性。
  • 最后,软件开发的真正进步依赖于编程技术的进步,而这又意味着编程语言的进步。C++就是这样的一个进步。它已经取得了爆炸式的流行,因为它是一门直接支持更好的软件设计的主要语言。
  • C++在正确的方向上迈出了一步,但是还需要更大的进步。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值