本章从一个吉他库存小程序开始,逐步改进,介绍了一些面向对象设计的核心思想。
初始场景
故事是这样开始的。。。。。。
有个老板是卖吉他的,它想要一个程序能管理和搜索他的吉他库存。
吉他店老板设计公司很快给出了最初的设计方案,有两个类:
- 吉他类:存储每一把吉他的特征
- 库存类:管理吉他库存
此外,还给出了这两个类的具体代码实现。但是某些代码似乎有点“坏味道”,特别是库存类中的 search
方法,非常长、非常复杂。
在模拟测试中,程序竟然没有按照预期工作:一把吉他明明存在,但是在库存中却查找不到
测试出现问题软件开发的终极问题
到目前为止,这个程序肯定是有问题的。
这时候,三个程序员出场了,他们对如何修改这个程序提出了各自的看法,似乎都有道理。
三人讨论他们三个人的讨论引出了终极问题:
- 什么是伟大的软件
- 怎么设计出伟大的软件
伟大软件满足两个标准:
- 符合用户的要求
- 可维护、可重用、可扩展
设计伟大软件有三个步骤:
- 首先满足用户的功能要求
- 引用 OO 原则
- 进行良好的设计
修正错误并改进
为了满足用户功能要求(伟大软件的第一步),要先修正原先程序中存在的错误,让程序首先能跑起来。
错误找到了,是字符串字面量大小写引起的
出错的地方对于如何修正这个错误,三个程序员又进行了讨论,他们认为在修正错误的同时,可以进行小的设计改进
讨论下面是三个改进的地方:
- 改进之一:用枚举常量替代字符串字面量来表示吉他的特征,这样可以避免拼写错误问题
- 改进之二:对于那些只能用字符串的特征,搜索的时候先将它们都转换成小写,然后再比较
- 改进三:原先的搜索功能只能返回第一个符合特征条件的吉他,现在可以将所有符合条件的吉他都返回
当然,每一次修改完都应该进行测试,这次测试实现了预期目标。
测试这一小节的核心思想是:软件开发一开始专注于实现功能,完成功能前不用太过在意设计问题。有点小的设计也无妨,但是实现功能是前期的重点
单一责任原则和封装
到目前为止,程序已经跑起来了,而且实现了预期的功能,但是仍然有一些地方似乎有问题。
如果要搜索具有某些特征的吉他,需要先用这些特征创建一个吉他对象,然后传给库存类的 search
方法进行查找。
问题是:一个完整的吉他对象有序列号和价格这两个属性。而搜索时创建的吉他对象并不需要这两个属性
搜索吉他为什么会这样?对于这个问题的分析引出了一个重要的原则:单一责任原则!
三个程序员进一步讨论,认为吉他对象承担了太多的责任,应该将特征部分放到另外一个对象中去
Paste_Image.png他们新创建了一个规格类,将吉他的特征部分放到这个类中
规格类这里还隐藏了一个原因:规格特征很容易发生变化,因此应该将他隔离出来,后面将会看到。
现在给库存类的 search
方法需要传送一个规格对象,而不是吉他对象,不再有多于的参数了!此外,比较的时候也是用规格对象,而不是吉他对象。
再次测试目前为止的代码,一切正常。
这一小节先引出了单一责任原则,最后告诉你用封装隔离变化,这些变化可能包括信息或者行为
复用
目前的代码已经不错了,但是它是不可复用的。
现在老板想增加一个“琴弦数目”的特征,对于目前这几个类,会造成什么影响呢?
增加属性影响如下:
- 规格类:需要增加“琴弦数目”属性,并增加相应的存取方法。(规格类的改动不算太大)
- 吉他类:吉他类的构造函数会接收一系列特征值,因此要增加一个参数以接收“琴弦数”;此外吉他类还需要创建规格对象,也需要增加这个参数。(吉他类的责任太多,创建规格类不应该是他的责任。同时他对规格类了解的太多。)
- 库存类:库存类
addGuitar
方法接收一系列特征值作为参数,因此也需要修改;此外,search
方法比较吉他的特征,要考虑新增加的属性。(库存类也是对规格类的细节了解的太多)
牵一发而动全身,说明代码的组织有问题
很难复用这一节的最后又引出了另外一个概念:委托。
委托最根本的目的就是划分责任,让每个对象只做自己该做的事情
重构代码
第一步:增加新的属性
增加新属性第二步修改:给吉他对象注入规格对象,而不是在吉他对象内部创建规格对象,解除强依赖
解耦第三步:将库存类 search
方法中的比对特征工作移动到规格类中,进一步解耦
最后再次测试,一切正常,得到一个设计良好的程序。
总结:开发设计三部曲
- 快速实现功能
- 用 OO 原则进行封装
- 重构、解耦
网友评论