posts - 101,  comments - 636,  trackbacks - 52

      在我上篇文章应用OOP的设计过程演化(一) 里,结合了实例通过应用OOP和重构等技术,你已看到代码是怎样一步一步复活的。让最初死板的代码变得灵活、可扩展,设计的不断演化过程证实,代码一步一步的复活就如同给一只冻僵翅膀的小鸟带去温暖的阳光一样。

      上一篇文章虽然算得上是完美的演义了一个应用OOP的设计过程,但缺点也不少,可能因为这样给文章留下了败笔。那下面我们就来分析下这些不足之出。我们在设计中为什么要不断的抽象,重构?因为最初的设计不可能是完美的,我们不能一下子就完全把各个对象、类、接口等角色的职责划分得清清楚楚,只有通过不断的对设计进行修改才能更进一步的分清各个角色的职责。

      “既然抽象销售业务的基类(Sel)l和租赁业务的基类(Hire)都具有相同的行为,这里我们完全可以在进一步的抽象,为什么不为这两个类定义一个统一的接口呢?”这是上一篇文章中留下的问题。是的,我们确实应该这么做:

 1/// <summary>
 2/// 系统总接口
 3/// </summary>

 4public interface IMoney
 5{
 6    /// <summary>
 7    /// 返回本次交易的金额
 8    /// </summary>
 9    /// <returns></returns>

10    double GetMoney();
11
12    /// <summary>
13    /// 执行某项特定操作(销售、出租、归还)
14    /// </summary>
15    /// <returns></returns>

16    string Execute();
17}


     此时,我们还需要修改Sell和Hire两个类,让其继承IMoney接口,如下UML图示:
                                                 
      此时,客户端调用就可以直接依赖于最高层抽象IMoney接口,是不是到此就画上完美的句号了呢?事实并非我们所想的那么简单,我们虽然已经抽象出了最高层次的接口,但是这样还是有所不足,那不足之处在哪里呢?解决这个问题之前我们先来分析下具体的业务逻辑。
      在一个书店的业务(不管是销售还是租借业务)里,只要存在业务关系,那就存在这这样的依赖,从第一篇文章(没有阅读过第一篇文章建议先阅读完第一篇文章:《书店信息系统》系列一----应用OOP的设计过程演化 )里的设计可以看出,每完成一笔业务交易,就会涉及到顾客类型(会员、普通顾客)、交易类型(出售、出租)、租借类型(租借、归还),我们还可以为书进行分类,比如生活类,小说类以及杂志等。既然有这样的关系存在,从设计上来说我们是不能在应用中强行来指定类型的,那应该怎么做呢?我们是不是应该对业务类型进行封装?这里我采用枚举:

 1/// <summary>
 2    /// 会员类型
 3    /// </summary>

 4    public enum U_Type
 5    {
 6        /// <summary>
 7        /// 会员
 8        /// </summary>

 9        MEMBER,
10
11        /// <summary>
12        /// 普通顾客
13        /// </summary>

14        SHOPPER
15    }

16
17    /// <summary>
18    /// 书的类型
19    /// </summary>

20    public enum B_Type
21    {
22        /// <summary>
23        /// 小说分类
24        /// </summary>

25        NOVEL,
26
27        /// <summary>
28        /// 生活百态
29        /// </summary>

30        LIFT,
31
32        /// <summary>
33        /// 杂志
34        /// </summary>

35        MAGAZINE
36    }

37
38    /// <summary>
39    /// 交易类型
40    /// </summary>

41    public enum S_Type
42    {
43        /// <summary>
44        /// 出售
45        /// </summary>

46        SELL,
47
48        /// <summary>
49        /// 出租
50        /// </summary>

51        HIRE
52    }

53
54    /// <summary>
55    /// 租借类型
56    /// </summary>

57    public enum H_Type
58    {
59        /// <summary>
60        /// 租借
61        /// </summary>

62        RENT,
63
64        /// <summary>
65        /// 归还
66        /// </summary>

67        BACK
68    }

    对也类型进行封装后,我们在次进一步的分析具体的业务逻辑,在书店业务里,每次交易是不是还存在着书名、客户(顾客)名、书的定价以及顾客所支付的现金呢?然后这些属性都是任何一笔业务交易都存在的,从对象的职责上来说,我们应该把这些属性建立在共性层次上,那是这样的吗?

 1public abstract class Root:IMoney
 2{
 3    protected U_Type _uType;     //会员类型
 4    protected B_Type _bType;     //书的类型
 5    protected S_Type _sType;     //交易类型
 6
 7    protected string _userName;  //用户名
 8    protected string _bookName;  //书名
 9    protected double _bookPrice; //书的定价
10    //实际支付现金,不管是租书还是还书还是买书,他都会涉及到最终给了多少钱这个问题
11    protected double _bookCash;  
12
13    /// <summary>
14    /// 处理租赁或销售的操作
15    /// </summary>
16    /// <returns></returns>

17    public abstract string Execute();
18
19    /// <summary>
20    /// 返回本次交易的金额
21    /// </summary>
22    /// <returns></returns>

23    public abstract double GetMoney();
24}
      从新建立了Root类,用来封装业务逻辑共享的属性,之前我们抽象出了最高层次的IMoney,Root除了封装属性外还应该具备共同的操作行为,而共同的行为已经定义在IMoney接口里,此时我们就可以一劳永逸地享受原有的设计了,只需要让Root继承于IMoney接口就OK。到此,系统的体系结构就算设计完成了。
                               

      应用体系设计完毕,那下面应该把全部精力投入到业务逻辑的分析上了。首先从销售逻辑出发,书店要销售出去一本书,那他首先要做的工作是什么?暴露书的属性:书名和定价还应该有买书的用户吧,其次还应该有客户所支付的现金。显然这些职责应该划分到销售书的父类(Sell)里,实际收取了多少钱这个还需要根据顾客的类型来决定具体采用何种收费策略,具体的收费策略的职责应该是具体的业务对象(Buy和SBuy)来完成,Sell作为父类,他所承担的职责是封装具体业务对象的共同属性和行为。详细如下:

 1namespace EBook.Step4
 2{
 3    /// <summary>
 4    /// 抽象出销售书的父类,所以的销售行为都继承于它。
 5    /// </summary>

 6    public abstract class Sell:Root
 7    {
 8        /// <summary>
 9        /// 初始化该类
10        /// </summary>
11        /// <param name="userName">用户名</param>
12        /// <param name="bookName">书名</param>
13        /// <param name="bookPrice">书的定价</param>

14        public Sell(string userName, string bookName, double bookPrice)
15        {
16            _userName = userName;
17            _bookName = bookName;
18            _bookPrice = bookPrice;
19        }

20
21        外露属性(用户名、书名、定价)