《深入浅出面向对象分析与设计》读书笔记
这本书整体在说些什么?
本书整体上主要讲如何使用 OO 的方式去编写程序,通过使用那些 OO 原则是如何使得程序变得高内聚低耦合,并在讲述的过程结合实例进行分析。
这本书的正确性?
本书作为一本定位为入门级别的书籍,再加上时代的发展,肯定有其局限性,但是全本看下来,大部分知识依旧是有用的,特别是讲练结合的教学方式,能够切实体验到各种抽象模式起到的作用和优势,作为一本入门面向对象编程的书籍再合适不过了。
其次,本书提供了一套采用面向对象思想开发程序的方法论,也许对于天才型程序员循规蹈矩是不合适的,但我相信对于大部分人来说,把自己编程实践中的经验与书籍中讲述的系统的方法论达成共识后,能够获得更深的感悟。你的经验不再停留的直觉阶段,而是系统可被论证的方法步骤,我相信这是极为有益的,无论是在阐述其优点时,还是讨论其相关的局限性时。
能学到什么?
对于不懂面向对象的人,可以学习到使用面向对象思想开发程序的方式
对于有经验的编程者,可以从书籍中印证自己多年实践得来的编程经验,使其更系统具体化
这本书细部说了什么?
第一章 良好应用程序的基石
本章由设计一个吉他售卖程序作为例子,介绍了 封装,委托 概念在实际编写程序的应用和好处,并给出了一个简单易行的开发优良软件的步骤:
- 确定需求
- 运行 OO 原则组织代码,增加代码灵活性
- 适当使用设计模式使得软件对 未来可能出现的扩展以及需求 友好
并且,特别强调了,对于一个应用程序来说:满足用户需求是首要条件
非常浅的提了下 TDD 开发。
暗示了一个可能不严谨的确定程序耦合程度的方法:当你需要增添新功能时,你的修改是否被局限在某一范围内,还是要对程序大动干戈
阐述了对于一个对象来说:
- 其命名应当与其责任有正确的逻辑关联
- 其本身应该代表某个单一概念,承担单一责任
- 其自身不应该存在无用的属性
第二章 收集需求
本章由设计一个智能狗门程序为例子,强调了 确定用户需求,写出需求列表 是非常重要的一步,并且要能考虑到用户没考虑的部分,一个比较有帮助确定需求的技术是编写使用用例,即对程序使用的步骤化描述,一个使用用例的组成:
- 所代表的价值,即这个用例表明了程序的什么能力?
- 起点和重点
- 外部启动者
需求列表需要满足的条件:
- 所含需求与客户的需求一致
- 涵盖所有的使用用例
介绍了编写用例时,主要路径(快乐路径)和次要路径的概念,主要路径是程序正常运行的每一步,次要路径是主要路径的细节或者额外的考量点
第三章 需求变更
本章接续上章智能狗门的例子,点出了软件设计与分析的一项真理:变更。并说明了当需求变更的时候,正确的应对方法是什么。首先需要记住的一点是:客户永远是对的,不要抵触变更,变更无法避免,同样的,面对变更你理所应当要求更多的时间和金钱
引入解释了一个用例相关的新概念 替换路径 ,作为某个主要路径的替换项,以一种不一样的方式来完成用例的目标,并且确保测试了所有路径的组合后的场景
提了一个 OO 原则:将可能变化的事物封装起来
第四章 分析
接续第三章的狗门例子,本章开头就指出了我们所编写的程序并不是运行在一个完美的世界,当运行在真实世界时必然会出现问题,我们必须 **考虑软件在各种现实场景下可能遭遇的问题。**为了尽可能减少现实世界问题对我们程序的影响,我们需要对程序进行 分析
分析的步骤:
- 识别问题
- 规划解决方案
- 更新用例
- 测试新用例能否处理先前的问题
根据用例进行文本分析来进行建模,但要注意建模对象的范围,系统外的对象一般不需要建模
本章最后,浅出了下 UML 类图
- 类图的作用主要在于协作
- 通过类图可以提前暴露一些错误,而修改类图远比修改代码容易
第五章 第一部分:良好的设计=灵活的软件
本章续接第一章吉他商店的例子,通过为吉他商店程序添加售卖班卓琴的能力,引入了虚拟基类的概念。自然而然的也讲述了继承的概念,将多个类相似的部分抽到基础类中继承,是另一种复用代码的手段
然而,这就是本书由于时间导致的局限性,经过编程界那么多年的发展,大家发现继承似乎并不是一个降低耦合性的编程实践,与抽成基类相比,更建议抽成接口然后去实现接口
不过本书在本章末尾还是点出了抽象基类的问题,当基类需要支持多种子类时,程序的耦合性又开始增加了,每一种新的子类都需要修改基类来适应子类
5.1 OO大灾难
本小节解释了什么是接口?什么是封装?什么是变更?
开发者应该保证每一个类只做一件事,如果一个类可能因为多个不同种类的原因修改自身逻辑,说明可能这个类承担了过多职责
新的 OO 原则
- 对接口编码,而非实现
- 类的职责应该是单一的
5.2 第二部分:给你的软件 30 分钟的伸展操-灵活的软件
本章开始引入继承,多态,抽象,封装等概念来在吉他商城程序实践减少程序耦合度的方法,这需要我们去修改前几章实现的代码,而进而引出了一个非常常见值得重视的现象:过早进行设计,会使得难以进行重构。同时也要知道,设计也是不断进步的,开发者不应该抵触新的设计。
对于跨对象变化的属性,在灵活性优先的情况下,我们可以使用 Map 数据结构来动态存储这些属性,这样以后增添删除都会很容易。
在通过应用各种抽象保证软件低耦合之后,开始引入讲述高内聚的概念,所谓的内聚指的是应用各个元素之间的连接程度,高内聚表明了元素自身的责任单一且明确。
此外,知道何时停止!当用户对程序满意,且程序运作正常,并且你已经尽力是它变得更好了,那么就该移动到下一个项目了。
新的 OO 原则
- 类是关于行为与功能的
第六章 解决大问题
本章开头解释了,前几章学到的方法同样适用于大问题,而诀窍在于将大问题视为一系列小问题的组合体。
然后从设计一个电子策略游戏框架开始,来应用前几章所讲述的规则,并同时带出一些新的概念。
再次重述,开发程序的第一步是确定用户的需求,但有时候用户的需求会过于宽泛,这个时候我们有必要进一步了解详细信息,在了解的过程中,我们需要特别关注那些 需要变化的部分,我们需要保证对应的程序代码有足够的灵活性,来应对未来的变化。
与需求相对应的一个概念是功能 (feature) ,面对 feature 开发可以帮助我们在面对大型程序不知从何下手时,给我们一个启动的入口。一个 featrue 可能满足多个需求,我们可以从客户的需求中推断出 featrue ,可以从 featrue 反推需求。然而话又说回来,其实 featrue 和需求之间的间隔很模糊,主要看个人对 featrue 和需求词语的理解
另一点是,当设计一个复杂的软件时,首先不要溺入到细节中(尝试编写用例什么的),只要可以,就尽量把细节往后拖,先从整体上把握你要开发的是什么。并介绍了一个帮助从整体上把握软件想做什么的工具——用例图,用例图是系统的蓝图,帮助你构建出软件的整体轮廓。
值得注意的一点:在考虑一个系统的使用者的时候,不要局限于人类,还有可能是另一个系统
使用用例图来聚焦软件的轮廓,使用 feature 列表来确定软件需要做到事情
本章同样谈到了领域分析,即以客户所熟知的术语来描述问题,不要和客户谈论代码,featrue 什么的,你需要把他们对应到客户熟知的术语。
在结尾,总结了本章讲述的解决 大问题 的步骤:
- 聆听客户
- 尝试理解系统
- 画出系统蓝图
- 分解大问题到小问题
- 识别适用于这个系统的设计模式
第七章 架构
在我的编程生涯中,我遇到最苦难的问题莫过于,在开始编写程序时,我想的是低耦合,高内聚,单一职责,接口开发,但是这些都是原则性的词语,这些词语都是名词,我需要的是动词,告诉我怎么去做,而我解决的办法是开始写面条式代码,而本章的主题就是讲解,在面对一个新程序时,如何去开始,而这个方法在第一章就提了,就是
- 确定需求
- 运行 OO 原则组织代码,增加代码灵活性
- 适当使用设计模式使得软件对 未来可能出现的扩展以及需求 友好
再具体点的步骤就是
- 从 feature 开始,确认程序该做的事情
- 确定最重要的 featrue
- 确定系统的架构
- 从所有部件开始
- 通过灵魂三问,找出最核心的部件
- 它是系统的本质吗?
- 这TM到底是什么意思?
- 该怎么去实现
- 根据系统在基本层次的表现形式来找出系统的本质
当我们在编写一个新程序时,所经历的状态时,我们开始获得了大量的信息,我们可以总结出来各种需求和 featrue ,然后我们经过精简,专注于系统应该做的事情上,最终筛选出系统的核心功能。当筛选出核心功能时,可能存在多个核心功能,此时选那个功能开始的依据是 **风险。**你应该选择风险最低的核心功能开始,而风险最低的含义是,功能含义没有模糊不清或可能推倒重来的概率很低。
当你对某项 featrue 感到迷惑时,你要做的是
- 询问客户——这个 feature 的意义
- 共同性分析——找出这个 feature 在不同场景下抽象的部分
- 实现计划
对于不通点比共通点多的情况,直接放手给客户也许是更好的选择
第八章 设计原则
这里开头就讲了一个编程界常见现象:没有什么比想出一个问题的全新解法更令人的兴奋的事情了,直到你看到已经有人想出来了,并且远比你的好,然后开始介绍已经被前人总结出来的一些编程范式——设计模式
举例介绍了,开闭原则,DRY 原则,单一职责原则,Liskov替换原则,委托,组合,聚合的好处。
说了下一般开发者对单一职责原则的误区,单一职责原则,一般是指一个类获一个函数只做一件事,但是这件事并不一定是小事,同样也可以是很大的事情,重在在于范围的划分是否明确
第九章 迭代与测试
开始指出了一点:伟大软件的编写是 迭代 进行的,从整体轮廓到片段,直到完成。这种迭代开发的方式存在两种:
- 功能驱动开发 - 适用于程序由不怎么相关的功能组成,容易出成果
从 feature 出发,规划、分析、开发,直至完成。
- 用例驱动开发 - 适用于长流程多场景的程序,相对慢一点
从用例出发,编写足以满足用例的程序代码
这两种方式的都是源于客户的需求,聚焦于完成客户需要的东西。
无论哪一种方式,在完成后,都要测试各种可能的结果,特别是不正确输入的结果,来保证程序的稳定性。
如何编写测试用例:
- 每个测试都有名称,描述了其做的事情
- 每个测试用例应该遵循单一职责原则,只有一件特定的事情要测试
- 测试用例需要输入
- 你需要确定测试用例需要的预期输出
- 确定测试的初始状态
讲述了库的开发者和使用者之间的两种编程方式:
- 契约式编程
对于开发者来说,契约式编程意味着相信使用者会考虑到和处理意外情况
对于使用者来说,则是相信开发者已经处理好文档外的异常情况,无需额外处理
- 防御性编程
对于开发者来说,防御性编程意味着不相信使用者会考虑到意外情况,因此选择抛出 Checked Exception
强制使用者处理异常
对于使用者来说,则是相信开发者没有处理好异常情况,因此编写各种防御性代码,如判断非 null , 捕获异常来防止异常情况
第十章 OOA&D 的生命周期
总结了前几章,具体的描述了下整个软件的开发流程
1 确定客户要做的事情:
- 功能列表
- 用例图
- 分解问题
- 确定需求
2 运用 OO 原则增加灵活性
- 领域分析
- 初步设计
3 努力实现可维护、可重用的软件
- 实现
- 交付
随后使用一个例子,讲练结合实践了下上述流程,最后说了些本书没有提到的 OO 概念