在我上篇文章应用OOP的设计过程演化(一) 里,结合了实例通过应用OOP和重构等技术,你已看到代码是怎样一步一步复活的。让最初死板的代码变得灵活、可扩展,设计的不断演化过程证实,代码一步一步的复活就如同给一只冻僵翅膀的小鸟带去温暖的阳光一样。
上一篇文章虽然算得上是完美的演义了一个应用OOP的设计过程,但缺点也不少,可能因为这样给文章留下了败笔。那下面我们就来分析下这些不足之出。我们在设计中为什么要不断的抽象,重构?因为最初的设计不可能是完美的,我们不能一下子就完全把各个对象、类、接口等角色的职责划分得清清楚楚,只有通过不断的对设计进行修改才能更进一步的分清各个角色的职责。
“既然抽象销售业务的基类(Sel)l和租赁业务的基类(Hire)都具有相同的行为,这里我们完全可以在进一步的抽象,为什么不为这两个类定义一个统一的接口呢?”这是上一篇文章中留下的问题。是的,我们确实应该这么做:
此时,我们还需要修改Sell和Hire两个类,让其继承IMoney接口,如下UML图示:
此时,客户端调用就可以直接依赖于最高层抽象IMoney接口,是不是到此就画上完美的句号了呢?事实并非我们所想的那么简单,我们虽然已经抽象出了最高层次的接口,但是这样还是有所不足,那不足之处在哪里呢?解决这个问题之前我们先来分析下具体的业务逻辑。
在一个书店的业务(不管是销售还是租借业务)里,只要存在业务关系,那就存在这这样的依赖,从第一篇文章(没有阅读过第一篇文章建议先阅读完第一篇文章:租借类型(租借、归还),我们还可以为书进行分类,比如生活类,小说类以及杂志等。既然有这样的关系存在,从设计上来说我们是不能在应用中强行来指定类型的,那应该怎么做呢?我们是不是应该对业务类型进行封装?这里我采用枚举:
对也类型进行封装后,我们在次进一步的分析具体的业务逻辑,在书店业务里,每次交易是不是还存在着书名、客户(顾客)名、书的定价以及顾客所支付的现金呢?然后这些属性都是任何一笔业务交易都存在的,从对象的职责上来说,我们应该把这些属性建立在共性层次上,那是这样的吗?
2
应用体系设计完毕,那下面应该把全部精力投入到业务逻辑的分析上了。首先从销售逻辑出发,书店要销售出去一本书,那他首先要做的工作是什么?暴露书的属性:书名和定价还应该有买书的用户吧,其次还应该有客户所支付的现金。显然这些职责应该划分到销售书的父类(Sell)里,实际收取了多少钱这个还需要根据顾客的类型来决定具体采用何种收费策略,具体的收费策略的职责应该是具体的业务对象(Buy和SBuy)来完成,Sell作为父类,他所承担的职责是封装具体业务对象的共同属性和行为。详细如下:
2
抽象业务层之下的具体业务对象,他门的职责就是完成具体的业务,根据我们之前的体系设计来分析,抽象销售业务(Sell)下有两个具体的业务对象(会员购书Buy和普通顾客购书SBuy),深入到具体的业务对象领域,之前我们为书分类了,那用户在购买书的时候在收费策略上肯定会判断书的类型,书的类型属性我们已经在抽象层Root里定义,这里我们只需要给他初始化下值就可以了(通过构造方法):
下面是具体的逻辑行为:
普通顾客的逻辑于会员的逻辑差不多,只是在收费的策略上有所不同,主要体现在折扣上。
此时的结构体系就应该是这样的:
销售业务分析完毕,接下来我们来看看租借业务的实现。在租借业务里存在着两种业务三个业务对象:出租,归还(会员和普通顾客),首先来分析出租的业务逻辑,我们回想到现实生活中的租书业务,租书的时候是不需要支付租金的,但是需要支付押金,而收取押金需要根据租借时间(天数)来计算。也就是说,在租借业务里出了从继承体系中继承而来的属性外,我们不得不在另外添加两个属性:租借天数和所交押金;这是租借(出租和归还)业务所共有的:
2
在具体的业务行为上和销售行为没有什么区别,详细定义如下:
实现基类的抽象方法,但是考虑到还需要再下级的派生类来完成,所以我们选择让他调用其他能够被派生类修改的方法,这也就把具体的逻辑派生到具体的业务对象去实现了,这里的具体业务对象也就是租借业务对象(Rent)、会员归还业务对象(MBack)和普通顾客归还业务对象(SBack)。
租借业务对象(Rent):
2
会员归还业务对象(MBack):还书的时候需要退还押金并支付租金,我们直接把租金在押金里面硬性扣除.不同的是这个是会员还书,所以租金和普通顾客的租金有区别。
2
普通顾客归还业务对象(SBack):还书的时候需要退还押金并支付租金,我们直接把租金在押金里面硬性扣除,和会员归还业务对象没什么大区别,只是在租金的算法上有点差异而已:
2
到此为止,整个系统的体系结构设计也业务逻辑实现都已经完成,我们可以于此画上个“句号”了。下面来写个程序简单测试下这五个具体业务对象。
2
测试结果如下图:
本文在原有的设计基础上又进行了修改,抽象出了抽象父类(Root)和总接口(IMoney),最终的设计如下示:
我们之前在设计的过程中已经抽象出了顶层接口IMoney,那么在客户端里我们可以直接使用IMoney接口来代替所有的抽象类,就上面的简单测试程序里,我们完全可以使用IMoney接口来代替Root,关于这点我将在后续文章里详细介绍,本文就介绍于此,我相信之前的设计过程演变+案例代码的展示+你自己学习后的总结会比我解说得更好。
相关文章连接:
应用OOP的设计过程演化(一)