本系列笔记都是记录设计模式的学习过程,会记录一些书中比较重要的东西和自己的心得,书名为《设计模式解析》。
功能分解
将一个问题分解成多个功能步骤,解决更小的问题要比解决整个问题更简单。
功能分解通常会有一个“主”程序负责控制子程序,主程序需要协调各个函数并控制它们的先后顺序。这种方法会产生两个问题:
- 如果问题比较大,写出的代码会非常复杂
- 如果需求改变,修改代码将十分麻烦
对于第一个问题,我们可以让子程序承担更多的责任,让子程序负责自己的行为,而且让主程序知道它可以执行这个任务,这样程序的逻辑就不会那么复杂了,这就是所谓的委托(delegation)。
对于开发人员来说,需求永远都不会是完整的、一成不变的,编写能够很好的处理需求变更的代码不是一件容易的事情。书中有这样一句话:“虽然我们无法预测会发生什么变化,但是我们通常可以预测到那里会发生变化。与其抱怨需求总是变化,不如改变开发过程,从而更有效的应对变化”。
面向对象的巨大优点之一,就是可以封装变化的区域,从而更容易的将代码与变化产生的影响隔离开来。使用模块化封装变化后,有新的需求时,只需要改变这个模块即可。但这种方法仍然存在问题,就是如果模块的输入数据如果发生变化,这种方法就不适用了。
模块化肯定有助于提升代码的可理解性,使代码更容易维护,但是模块化并不能应对所有的需求变化。这种方法存在两个问题:低内聚和紧耦合。
- 内聚性(cohesion):有人成为清晰性(clarity),指的是“例程(可以理解为类)中操作(理解为函数)之间联系的紧密程度”。低内聚指的是一个类任务很多而且互不相关,代码让人看起来想是令人疑惑的一大团。有人称之为“上帝对象”,因为他们好像是万能的。。。
- 耦合性(coupling):指的是“两个例程之间联系的紧密程度”。
耦合性与内聚性是相辅相成的关系,内聚性描述的是内部组成部分之间的相互关联的紧密程度,而耦合性是指例程之间联系的紧密程度。软件开发的目标应该是创建这样的例程:内部完整(高内聚),而与其他例程之间的联系则是小巧、直接、可见、灵活的(松耦合)。
对于程序员来说,最痛苦的莫过于调试bug,但是更痛苦的是调试完这个bug,却对代码的其它地方造成了意想不到的影响,事实上,我们花费了更多的时间去修复因为修复bug而导致的其它问题。
这就是功能分解的弊端,需求的变更会对软件的开发和维护产生极大的影响。对一组函数或者数据的修改将会影响到其它的函数和数据,就像滚雪球一样,会导致更多的变化,而且难以避免。
应对需求变更
为了找出解决需求变更问题的解决办法,找到代替功能分解的方法,下面通过一个生活中的例子进行说明:假设你是一个会议上的讲师,听课的人在课后还要去听其它的课,但他们不知道下一堂课的听课地点。你的责任之一就是确保大家都能顺利的去上下一堂课。
按照结构化程序设计方法,一般会通过下面方法:
- 获取听课人的名单
- 对名单上的每个人进行下面操作
- 找到他下一堂要听的课
- 找到该课的听课地点
- 找到去下一节课怎么走
- 告诉这个人怎么去上下一堂课
这种方法只是理论上的方法,如果真的在现实生活中去做,估计没有一个人会这样做。最可能的做法就是把所有到其它教室的路线写到黑板上,然后告诉他们让他们自己去下一个教室。
第一种方法你需要找到每一个人,告诉他们怎么做,要对一切负责。而第二种方法只需要给出提示,每个人就可以自己去完成,每个学生对自己负责。
现在如果需求进行了变更,需要一些研究生在上下一节课之前先收集学生对本节课的评价并交到办公室。如果是使用上面第一种方法,就需要修改控制程序,判断学生是普通学生还是研究生,然后给研究生额外的任务,对程序的改动较大。如果是使用第二种方法,则不用修改控制程序,只需要再为研究生编写一个模块即可。
这两种方法的区别就是谁负责,第一种方法是主程序负责,而第二种方法将责任转移到了每一个学生的身上。
为了便于理解,书中给出了下面三个概念。在UML Distilled一书中,Martin Fowler描述了软件开发过程中的三个不同视角:
- 概念:这种视角呈现了所研究领域中的各种概念,得出概念模型时应该很少或者不考虑实现它的软件。这个视角要回答的问题是:软件要负责什么?
- 规约:这种视角考虑的是软件,但关注的是软件的借口,而不是实现。这个视角要回答的问题是:怎么使用软件?
- 实现:这是我们考虑的是代码本身,这可能是最常用的视角,但在许多方面,采取规约视角经常会更好。这个视角回答的问题是:软件怎样履行自己的责任?
结合上面的例子,作为讲师你是在概念层次上与人交流,换句话说你要告诉学生的是“你要他们做什么”,而不是“要他们如何去做”。但是,作为学生则是非常明确的知道自己该如何去下一堂课的教室,这是在实现层进行的。
在概念层上交流,在实现层上执行,这样主控程序就无需准确的知道操作具体细节,只需要概念性的知道即可。只要概念不变,请求者就与实现细节的变化隔离开了。
面向对象泛型
对象是什么?对象传统上被定义为带有方法的数据。
使用对象的优点在于可以定义自己负责自己的事物。对象天生就知道自己的类型,对象中的数据能够告诉它自己是什么状态,而对象中的代码能够让它知道自己能做什么。
联系上面说的软件开发的三个视角,可以这样理解面向对象:
- 在概念层次上,对象是一组责任。
- 在规约层次上,对象是一组可以被其它对象或对象自己调用的方法。
- 在实现层次上,对象是代码和数据,以及他们之间的计算交互。
类是对对象行为的定义,它包含以下内容的完整描述:
- 对象所包含的数据元素。
- 对象能够操作的方法。
- 访问这些数据元素和方法的方式。
要获得一个对象时,要告诉程序需要某一个类型(也就是对象所属的类)的一个新对象,这个新对象为类的一个实例。创建对象的过程成为实例化。
面向对象的三个特征是封装、继承、多态。