第五章(重点,精华)
Part 1
Good design = flexible software
Nothing Ever Stays The Same
前言:
这一章彻底的展示出好的设计对于项目的改变的适应性,相比较而言,对于普通的设计,某个地方的改变可能会给项目带来巨大的修改(灾难),这一章回到了吉他的那个例子,向我们展示客户需求变化引起系统功能变化对整个系统的改变,以及给出了十分有弹性的解决方案。
案例分析:
案例描述:
吉他店的老板想在吉他店里卖其他乐器—曼陀林,要求对这个销售吉他的程序进行修改
问题提出:
1 根据先前的设计,如下图,怎么做出改变来达到客户要求?
问题解决:
1 我们很容易就会想到吉他和曼陀林都属于乐器,只要建立一个乐器类(抽象基类),然后使吉他类和曼陀林类都继承自乐器类,将他们共有的属性方法提升到乐器类里,如下图
同样的我们也需要一个Spec基类作为GuitarSpec和MandolinSpec的父类,如下图
{zh1}修改一下Inventory类来完成查找,添加等功能
到了这里,我们或许会觉得这样的设计已经很xx了(如果是我,我也会这样设计。。)
但是。。Guitar和mandolin类里面几乎什么都没有,addInstrument函数里面又有太多其他交互。。。而这就引出了第二个问题
问题提出:
1 我们在前面的解决方法中用了“封装”,用了“多态”,但是如果我们要新增加一个类别的乐器,我们还是会动很大的工,修改很多东西
问题解决:
1 这个设计方案实际上并没有达到松散耦合的要求,所有东西的pretty tightly connected, One of the best way to see if software is well-designed is to try and to change it,所以我们试着自己做出改变(我们尝试增加好几个新类型的乐器,这样一来我们发现,我们每次都要继承Instrument类和InstrumentSpec类,然后还要在Inventory类里面的addInstrument和search函数中修改,这样可以看出我们的设计有很大缺陷)
为了给予我们解决的思路和办法,文章教了我们三种OO的技术:
(1) Coding to an interface , rather than to an implementation makes your software easier to extend. By coding to an interface , your code will work with all of the interface’s subclasses-even ones that haven’t been created yet. 这里举了个运动员的例子来说明这个原则,Team类的成员函数为addPlayer(未知要传入的参数类型),这时候我们应该传入一个“athlete”接口的参数,而不是某个具体的运动员类(例如FootballPlayer),这样一来,我们可以借由athlete这个借口(它提供了普通运动员类所有的行为接口),来适应变化,即使是还没创建的其他运动员类,也可以通过实现这个接口来传入到这个函数里,coding to the interface, not the implementation.
(2) 封装不仅能防止重复的代码,还能protect your classes from unnecessary changes。每当你认为你的程序里的某些behavior很可能会改变时,你必须将这些容易改变的代码重不容易变化的地方移走,所有就是说encapsulate what varies , 这里同样举了个例子来说明,由于和一起学过的“中间类”的概念有点类似,所有这里就不多细说,见下图
(3) The easiest way to make your software resilient to change is to make sure each class has only one reason to change
这里也类似前面说到了—一个类只要负责好一件事,不能做太多事,所以这里也不详述(可以对类进行询问how,问他怎么样完成它能完成的事,如果回答是多变的,那么那部分很可能就是变化大的代码块)
(4) {zh1}是一个小型程序设计,喊你用上面三种方法来完善。
首先在DessertCounter内,我们可以使用Dessert接口,来将Orderxx这两个函数变为OrderDessert,返回类型还是Dessert,然后我们还发现这个类里既有orderxx又有addxx,这使得这个类有多种可能的机会去改变,所以这个类里面有不是他的责任的函数存在,所以应该把addTopping方法放到Dessert类里。
然后我们发现好多个类里都有serve函数,所以需要采取第2种方面(也就是中间类的方法)建立一个DessertServer类来完成serve行为,这样就封装了what varies,而且也不会出现波纹效应,然后Topping和IceCream可以提取一个基类出来封装common
总结就是看Important point 第7点
Important Point:
1 Whenever you find common behavior in two or more places, look to abstract that behavior into a class, and then reuse that behavior in the common classes.
2 One of the best way to see if software is well-designed is to try and to change it.
3 If ease of change is how we determine if our software is well-designed, then we’ve got some issues here.
4 Interface is the code construct that has the dual role of refining behavior that applies to multiple types, and also being the preferred focus of classes that use those type.
5 Encapsulation is responsible for preventing more maintenance problems than any other OO principle in history, by localizing the changes required for the behavior of an object to array.
6 Every class should attempt to make sure that it has only one reason to ”CHANGE”, the death of many a badly designed piece of software
7 OO Principles
1 Encapsulate what varies
2 Code to interface rather than to an implementation
3 each class in your application should have only one reason to change
小结:这一章和{dy}章有同样的精彩之处,带给我们设计的艺术美感。很值得思考其中的东西。