应用OOP的设计过程演化(二)

应用OOP的设计过程演化(二)
 

2009-11-16 作者:beniao 来源:beniao的BLOG

 

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

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

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

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

 4 public   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     }

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

 1 public   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作为父类,他所承担的职责是封装具体业务对象的共同属性和行为。详细如下:

 1 namespace  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          外露属性(用户名、书名、定价)
56
57          实现基类中的抽象方法以及将任务分派到下面的派生类去  
73
74          ///   <summary>
75          ///  处理销售书的方法
76          ///   </summary>

77          public   abstract   string  TExecute();
78
79          ///   <summary>
80          ///  返回本次交易的金额
81          ///   </summary>

82          public   abstract   double  TGetMoney();
83     }

84 }

85

抽象业务层之下的具体业务对象,他门的职责就是完成具体的业务,根据我们之前的体系设计来分析, 抽象销售业务(Sell)下有两个具体的业务对象(会员购书Buy和普通顾客购书SBuy),深入到具体的业务对象领域,之前我们为书分类了,那用户在购 买书的时候在收费策略上肯定会判断书的类型,书的类型属性我们已经在抽象层Root里定义,这里我们只需要给他初始化下值就可以了(通过构造方法):

 1 ///   <summary>
 2 ///  
 3 ///   </summary>
 4 ///   <param name="bType"> 书的类型 </param>
 5 ///   <param name="userName"> 用户名 </param>
 6 ///   <param name="bookName"> 书名 </param>
 7 ///   <param name="bookPrice"> 书的定价 </param>

 8 public  Buy(B_Type bType,  string  userName,  string  bookName,  double  bookPrice)
 9     :  base (userName, bookName, bookPrice)
10 {
11     _bType = bType;
12 }

下面是具体的逻辑行为:

 1 ///   <summary>
 2 ///  根据书的类型来定折扣
 3 ///  当然,这里的折扣本来是应该从数据库或者配置文件中取的,我们演示就固化到这里。
 4 ///   </summary>
 5 ///   <returns></returns>

 6 public   override   double  TGetMoney()
 7 {
 8      switch  (_bType)
 9      {
10          case  B_Type.NOVEL: BookCash = BookPrice *  0.9 ;
11              break ;
12          case  B_Type.LIFT: BookCash = BookPrice *  0.7 ;
13              break ;
14          case  B_Type.MAGAZINE: BookCash = BookPrice *  0.8 ;
15              break ;
16     }

17      return  BookCash;
18 }

19
20 ///   <summary>
21 ///  执行插入数据库的操作,但是我们这里不需要,只要把结果显示出来
22 ///  所以我们让他给我们返回一句话就OK了。
23 ///   </summary>
24 ///   <returns></returns>

25 public   override   string  TExecute()
26 {
27      return   string .Format( "尊敬的会员:{0},您购买《{1}》,定价为:{2}元,折扣后为:{3}元" ,
28         UserName, BookName, BookPrice, BookCash);
29 }

普通顾客的逻辑于会员的逻辑差不多,只是在收费的策略上有所不同,主要体现在折扣上。

 1 ///   <summary>
 2 ///  普通顾客购书
 3 ///   </summary>

 4 public   class  SBuy:Sell
 5 {
 6      public  SBuy(B_Type bType,  string  userName,  string  bookName,  double  bookPrice)
 7         :  base (userName, bookName, bookPrice)
 8      {
 9         _bType = bType;
10     }

11
12      public   override   double  TGetMoney()
13      {
14          switch  (_bType)
15          {
16              case  B_Type.NOVEL: BookCash = BookPrice *  0.8 ;
17                  break ;
18              case  B_Type.LIFT: BookCash = BookPrice *  0.4 ;
19                  break ;
20              case  B_Type.MAGAZINE: BookCash = BookPrice;   //不打折
21                  break ;
22         }

23          return  BookCash;
24     }

25
26      public   override   string  TExecute()
27      {
28          return   string .Format( "尊敬的顾户:{0},您购买《{1}》,定价为:{2}元,折扣后为:{3}元" ,
29             UserName, BookName, BookPrice, BookCash);
30     }

31 }

此时的结构体系就应该是这样的:

销售业务分析完毕,接下来我们来看看租借业务的实现。在租借业务里存在着两种业务三个业务对象: 出租,归还(会员和普通顾客),首先来分析出租的业务逻辑,我们回想到现实生活中的租书业务,租书的时候是不需要支付租金的,但是需要支付押金,而收取押 金需要根据租借时间(天数)来计算。也就是说,在租借业务里出了从继承体系中继承而来的属性外,我们不得不在另外添加两个属性:租借天数和所交押金;这是 租借(出租和归还)业务所共有的:

1 private   int  _day;
2 private   double  _deposit;

同样,我们还得对外暴露属性,包括用户名、书名、定价、租借天数、押金和实收现金:

 1 ///   <summary>
 2 ///  用户名
 3 ///   </summary>

 4 public   string  UserName
 5 {
 6      get   return  _userName; }
 7 }

 8
 9 ///   <summary>
10 ///  书名
11 ///   </summary>

12 public   string  BookName
13 {
14      get   return  _bookName; }
15 }

16
17 ///   <summary>
18 ///  书的定价
19 ///   </summary>

20 public   double  BookPrice
21 {
22      get   return  _bookPrice; }
23 }

24
25 ///   <summary>
26 ///  租借天数
27 ///   </summary>

28 public   int  Day
29 {
30      get   return  _day; }
31      set   { _day = value; }
32 }

33
34 ///   <summary>
35 ///  押金
36 ///   </summary>

37 public   double  Deposit
38 {
39      get   return  _deposit; }
40      set   { _deposit = value; }
41 }

42
43 ///   <summary>
44 ///  实收现金
45 ///   </summary>

46 public   double  BookCash
47 {
48      get   return  _bookCash; }
49      set   { _bookCash = value; }
50 }

在具体的业务行为上和销售行为没有什么区别,详细定义如下:

 1 实现基类中的抽象方法以及将任务分派到下面的派生类去
18
19 ///   <summary>
20 ///  处理租赁书的方法
21 ///   </summary>

22 public   abstract   string  TExecute();
23
24 ///   <summary>
25 ///  返回本次交易的金额
26 ///   </summary>

27 public   abstract   double  TGetMoney();

实现基类的抽象方法,但是考虑到还需要再下级的派生类来完成,所以我们选择让他调用其他能够被派 生类修改的方法,这也就把具体的逻辑派生到具体的业务对象去实现了,这里的具体业务对象也就是租借业务对象(Rent)、会员归还业务对象(MBack) 和普通顾客归还业务对象(SBack)。
租借业务对象(Rent):

 1 namespace  EBook.Step4
 2 {
 3      ///   <summary>
 4      ///  租书
 5      ///  分析:租书的时候是不需要支付租金的,但是需要支付押金
 6      ///   </summary>

 7      public   class  Rent:Hire
 8      {
 9          ///   <summary>
10          ///  初始化该类
11          ///   </summary>
12          ///   <param name="userName"> 用户名 </param>
13          ///   <param name="bookName"> 书名 </param>
14          ///   <param name="bookPrice"> 书的定价 </param>
15          ///   <param name="deposit"> 押金 </param>
16          ///   <param name="bookCash"> 实际支付 </param>

17          public  Rent( string  userName,  string  bookName,  double  bookPrice,  double  bookCash)
18          {
19             _userName = userName;
20             _bookName = bookName;
21             _bookPrice = bookPrice;
22             Deposit = bookCash;   //押金也就是租书的时候实际支付的现金
23             _bookCash = bookCash;
24         }

25
26          ///   <summary>
27          ///  执行出租逻辑
28          ///   </summary>

29          public   override   string  TExecute()
30          {
31              return   string .Format( "尊敬的顾客:{0},您租借《{1}》,本书定价为:{2}元,你支付押金:{3}元,实际支付{4}元" ,
32                 UserName, BookName, BookPrice, Deposit, BookCash);
33         }

34
35          ///   <summary>
36          /// 返回本笔交易的金额
37          ///   </summary>

38          public   override   double  TGetMoney()
39          {
40              //直接返回实际支付的现金
41              return  BookCash;
42         }

43     }

44 }

会员归还业务对象(MBack):还书的时候需要退还押金并支付租金,我们直接把租金在押金里面硬性扣除.不同的是这个是会员还书,所以租金和普通顾客的租金有区别。

 1 namespace  EBook.Step4
 2 {
 3      ///   <summary>
 4      ///  会员还书
 5      ///  分析:还书的时候需要退还押金并支付租金,我们直接把租金在押金里面硬性扣除.
 6      ///  不同的是这个是会员还书,所以租金和普通顾客的租金有区别.
 7      ///   </summary>

 8      public   class  MBack:Hire
 9      {
10          ///   <summary>
11          ///  初始化对象
12          ///   </summary>
13          ///   <param name="userName"> 用户名 </param>
14          ///   <param name="bookName"> 书名 </param>
15          ///   <param name="day"> 租借天数 </param>
16          ///   <param name="bType"> 图书类型 </param>

17          public  MBack( string  userName,  string  bookName,  int  day, B_Type bType,  double  deposit)
18          {
19             _userName = userName;
20             _bookName = bookName;
21             Day = day;
22             _bType = bType;
23              //实际开发中这里的租金应该是在借书的时候交的租金,这里是做演示就写死在这里了。
24             Deposit = deposit;
25         }

26
27          ///   <summary>
28          ///  执行还书的操作逻辑
29          ///   </summary>

30          public   override   string  TExecute()
31          {
32              return   string .Format( "尊敬的会员,您租借《{0}》,共计:{1}天,已支付押金{2}元,实际产生租金{3}元,应找回您{4}元。" ,
33                 BookName, Day, Deposit, GetRent(), TGetMoney());
34         }

35
36          ///   <summary>
37          ///  直接应该退给顾客多少钱
38          ///   </summary>

39          public   override   double  TGetMoney()
40          {
41              //直接返回应找回的现金,直接把租金在押金里面硬性扣除
42              return  Deposit - GetRent(); //押金减去租金=退还给顾客的钱
43         }

44
45          ///   <summary>
46          ///  计算书的租金
47          ///   </summary>
48          ///   <returns></returns>

49          private   double  GetRent()
50          {
51              switch  (_bType)
52              {
53                  case  B_Type.NOVEL: BookCash = Convert.ToDouble(Day) *  0.1 ;
54                      break ;
55                  case  B_Type.LIFT: BookCash = Convert.ToDouble(Day) *  0.5 ;
56                      break ;
57                  case  B_Type.MAGAZINE: BookCash = Convert.ToDouble(Day) *  0.3 ;
58                      break ;
59             }

60              return  BookCash;
61         }

62     }

63 }

普通顾客归还业务对象(SBack):还书的时候需要退还押金并支付租金,我们直接把租金在押金里面硬性扣除,和会员归还业务对象没什么大区别,只是在租金的算法上有点差异而已:

 1 namespace  EBook.Step4
 2 {
 3      ///   <summary>
 4      ///  普通顾客还书
 5      ///  分析:还书的时候需要退还押金并支付租金,我们直接把租金在押金里面硬性扣除
 6      ///   </summary>

 7      public   class  SBack:Hire
 8      {
 9          ///   <summary>
10          ///  初始化对象
11          ///   </summary>
12          ///   <param name="userName"> 用户名 </param>
13          ///   <param name="bookName"> 书名 </param>
14          ///   <param name="day"> 租借天数 </param>
15          ///   <param name="bType"> 图书类型 </param>

16          public  SBack( string  userName,  string  bookName,  int  day, B_Type bType,  double  deposit)
17          {
18             _userName = userName;
19             _bookName = bookName;
20             Day = day;
21             _bType = bType;
22             Deposit = deposit;
23         }

24
25          ///   <summary>
26          ///  执行还书的操作逻辑
27          ///   </summary>
28          ///   <returns></returns>

29          public   override   string  TExecute()
30          {
31              return   string .Format( "尊敬的顾客,您租借《{0}》,共计:{1}天,已支付押金{2}元,实际产生租金{3}元,应找回您{4}元。" ,
32                 BookName, Day, Deposit, GetRent(), TGetMoney());
33         }

34
35          ///   <summary>
36          /// 直接应该退给顾客多少钱
37          ///   </summary>

38          public   override   double  TGetMoney()
39          {
40              //直接返回应该找零的现金,已交的押金减去实际的租金
41              return  Deposit - GetRent();
42         }

43
44          ///   <summary>
45          ///  计算租金
46          ///   </summary>
47          ///   <returns></returns>

48          private   double  GetRent()
49          {
50              switch  (_bType)
51              {
52                  case  B_Type.NOVEL: BookCash = Convert.ToDouble(Day) *  0.1 ;
53                      break ;
54                  case  B_Type.LIFT: BookCash = Convert.ToDouble(Day) * 1d;
55                      break ;
56                  case  B_Type.MAGAZINE: BookCash = Convert.ToDouble(Day) *  0.5 ;
57                      break ;
58             }

59              return  BookCash;
60         }

61     }

62 }

到此为止,整个系统的体系结构设计也业务逻辑实现都已经完成,我们可以于此画上个“句号”了。下面来写个程序简单测试下这五个具体业务对象。

 1 namespace  EBook.Step4
 2 {
 3      class  Program
 4      {
 5          static   void  Main( string [] args)
 6          {
 7             Root root =  new  Buy(B_Type.LIFT,  "beniao" "Design Pattern" 50.30 );
 8             Console.WriteLine( "本次交易金额为:"  + root.GetMoney());
 9             Console.WriteLine( root.Execute());
10
11             Console.WriteLine( "/n--------------------------------------------/n" );
12
13             root =  new  SBuy(B_Type.MAGAZINE,  "beniao" "Design Pattern" 50.30 );
14             Console.WriteLine( "本次交易金额为:"  + root.GetMoney());
15             Console.WriteLine(root.Execute());
16
17             Console.WriteLine( "/n--------------------------------------------/n" );
18
19             root =  new  Rent( "beniao" "Design Pattern" 38.60 , 100.00 );
20             Console.WriteLine( "本次交易金额为:"  + root.GetMoney());
21             Console.WriteLine(root.Execute());
22
23             Console.WriteLine( "/n--------------------------------------------/n" );
24             root =  new  MBack( "beniao" "C#" 5 , B_Type.LIFT, 100.00 );
25             Console.WriteLine( "本次交易金额为:"  + root.GetMoney());
26             Console.WriteLine(root.Execute());
27
28             Console.WriteLine( "/n--------------------------------------------/n" );
29             root =  new  SBack( "beniao" "C#" 5 , B_Type.LIFT, 100.00 );
30             Console.WriteLine( "本次交易金额为:"  + root.GetMoney());
31             Console.WriteLine(root.Execute());
32
33         }

34     }

35 }

测试结果如下图:

本文在原有的设计基础上又进行了修改,抽象出了抽象父类(Root)和总接口(IMoney),最终的设计如下示:

我们之前在设计的过程中已经抽象出了顶层接口IMoney,那么在客户端里我们可以直接使用 IMoney接口来代替所有的抽象类,就上面的简单测试程序里,我们完全可以使用IMoney接口来代替Root,关于这点我将在后续文章里详细介绍,本 文就介绍于此,我相信之前的设计过程演变+案例代码的展示+你自己学习后的总结会比我解说得更好。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tof21

支持原创

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值