应用OOP的设计过程演化

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


面向对象的程序设计(Object-Oriented Programming,简记为OOP)立意于创建软件重用代码,具备更好地模拟现实世界环境的能力,这使它被公认为是自上而下编程的优胜者。它通过给程序中加入扩展语句,把函数“封装”进编程所必需的“对象”中。面向对象的编程语言使得复杂的工作条理清晰、编写容易。

在计算时代的早期,程序员基于语句思考编程问题。到了20世纪七八十年代,程序员开始基于子程序去思考编程。进入21世纪,程序员以类为基础思考编程问题。而类是OOP中的核心组成元素,通常都是使用类来“封装”对象(属性、行为)。在经典图书《代码大全》里定义:“创建高质量的类,第一步,可能也是最重要的一步,就是创建一个好的接口。这也包括了创建一个可以通过接口来展现的合理的抽象,并确保细节仍被隐藏在抽象背后。”

为了更好的理解设计思想,本系列文章以简单的《书店信息系统》为例,但随着需求的增加,程序将越来越复杂。此时就有修改设计的必要,重构和设计模式就可以派上用场了。最后当设计渐趋完美后,你会发现,即使需求不断增加,你也可以神清气闲,不用为代码设计而烦恼了。

在一个书店里,主要业务就是销售书,销售书后所得到的就是收取到的资金(本次交易金额),那以这个业务来分析,在不考虑设计的情况下,我们该怎么去实现:

 1 namespace EBook.Step1
 2 {
 3    /// <summary>
 4    /// 会员购书
 5    /// </summary>

 6    public class Buy
 7    {
 8        /// <summary>
 9        /// 处理销售书的方法
10        /// </summary>

11        public void Execute()
12        {
13            Console.WriteLine("会员购买了一本书");
14        }

15
16        /// <summary>
17        /// 买书得到了多少钱
18        /// </summary>

19        public void GetMoney()
20        {
21            Console.WriteLine("收到了xx.xx元RMB");
22        }

23    }

24}

这是针对书店的会员购书的业务逻辑,那如果是普通的顾客来购书呢?此时我们不得不为普通的顾客提供专门的服务(建立普通顾客业务逻辑类):

 1 namespace EBook.Step1
 2 {
 3    /// <summary>
 4    /// 普通顾客购书
 5    /// </summary>

 6    public class SBuy
 7    {
 8        public void Execute()
 9        {
10            Console.WriteLine("普通顾客购买了一本书");
11        }

12
13        public void GetMoney()
14        {
15            Console.WriteLine("收到了xx.xx元RMB");
16        }

17    }

18}

而客户端通过判断顾客的类型来决定调用具体的类来处理相应的操作:

 1 namespace EBook.Step1
 2 {
 3    class Program
 4    {
 5        static void Main(string[] args)
 6        {
 7            string uType = Console.ReadLine();
 8            switch (uType)
 9            {
10                case "会员": Member(); break;
11                case "普通顾客": General(); break;
12            }

13        }

14
15        private static void General()
16        {
17            SBuy sbuy = new SBuy();
18            sbuy.Execute();
19            sbuy.GetMoney();
20        }

21
22        private static void Member()
23        {
24            Buy buy = new Buy();
25            buy.Execute();
26            buy.GetMoney();
27        }

28    }

29}

仔细分析这段代码,虽然我们已经应用了OO的思想,将不同的顾客分为不同的对象来处理,但这样的设计同样很糟糕。也许你已经看出,这糟糕之处就是在switch这里。

不错,如果书店的客户不只是上述所提到的两种类型,还有如黄金会员,白金会员,白银会员,普通会员......等等一系列的划分,随着业务的扩展,将来会员类型也许还会不断的增加,那么就会去修改switch不断的增加相应的会员处理逻辑,然后让switch子句越来越长,直至达到你需要无限的拉动滚动条才能看到switch的结束。如上设计的UML图如下:

在上面的设计中,我们已经应用到了OO的思想,把不同个顾客类型做为一独立的对象来处理。仔细观察,会员(Buy)和普通顾客具有完全相同的方法,为什么不为它们建立一个共同的父类呢?

通过共性的抽象,让会员和普通顾客都去继承并实现父类的抽象方法,那代码是这样的吗?

 1 /// <summary>
 2/// 抽象出销售书的父类,所以的销售行为都继承于它。
 3/// </summary>

 4 public  abstract  class Sell
 5 {
 6    /// <summary>
 7    /// 处理销售书的方法
 8    /// </summary>

 9    public abstract void Execute();
10
11    /// <summary>
12    /// 卖书得到了多少钱
13    /// </summary>

14    public abstract void GetMoney();
15}

16 -----------------------------------------------
17 /// <summary>
18/// 会员购书
19/// </summary>

20 public  class Buy:Sell
21 {
22    public override void Execute()
23    {
24        Console.WriteLine("会员购买了一本书");
25    }

26
27    public override void GetMoney()
28    {
29        Console.WriteLine("收到了xx.xx元RMB");
30    }

31}

32 ----------------------------------------------
33 /// <summary>
34/// 普通顾客购书
35/// </summary>

36 public  class SBuy:Sell
37 {
38    public override void Execute()
39    {
40        Console.WriteLine("普通顾客购买了一本书");
41    }

42
43    public override void GetMoney()
44    {
45        Console.WriteLine("收到了xx.xx元RMB");
46    }

47}

我们通过抽象,引用了继承的思想,使整个设计也有了OOP的味道。

然而从现实生活中来分析,销售逻辑是一个抽象层,而我们所针对的是具体的顾客类型,出了在程序里应用多态特性,Sell类并没有实际使用的情况,这就是为何将其设计为抽象类及抽象方法,而不是使用普通的类里定义虚方法(virtual)的方式来实现。对应在设计中,就是:这个类永远不会被实例化,实例化的是它的子类。

此时,客户端的调用可以直接依赖于抽象层(Sell),不过这样的设计实质并没有多大的变化,客户端还是需要通过判断决定该调用那一个具体的实现。

 1 public  class Resolve
 2 {
 3    /// <summary>
 4    /// //依赖于抽象
 5    /// </summary>
 6    /// <param name="sell"></param>

 7    public void Execute(Sell sell)
 8    {
 9        sell.Execute();
10        sell.GetMoney();
11    }

12}

13
14 namespace EBook.Step2
15 {
16    //依赖于抽象,有了继承,有了OO的味道。
17    class Program
18    {
19        static void Main(string[] args)
20        {
21            //会员
22            new Resolve().Execute(new Buy());
23            Console.WriteLine("\n-----------------------------\n");
24            //普通顾客
25            new Resolve().Execute(new SBuy());
26        }
 
27    }

28}

29

这里我们先不谈客户端调用去判断顾客的类型。从现在的设计来看,即满足了类之间的层次关系,同时又保证了类的最小化原则,更利于扩展。即使你现在要增加如黄金会员(Gold)和白金会员(Platinum)等会员类型,只需要设计Gold和Platinum类,并继承Sell,重写Execute和GetMoney方法即可,而Resolve类对象的Execute方法根本就不用改变。

针对如上的设计来说,完全可以满足一个简单的销售逻辑的处理,可算是一个完美的设计。然而,在我们的实际项目中会有很多意想不到的事发生,其中需求变更应该是最为头疼的。刁钻的客户是永远不会满足的,这意味着我们就不能给我们的设计画上圆满的句号。时间久了,书店的一些书籍早已因陈旧而不能销售出去,可老板又不想让这些书成为废品,书无论是新还是旧都有他的价值所在,旧书的里的知识或许是不能与新版的书籍比配,但还是有一定的参考价值,就如我们去研究历史一样,是为了什么?是为了更好的迎接未来。

书店的业务扩展,老板决定将陈旧的书籍用来出租(呵呵,这想法不错,满足了像我这样的穷人想看书可又没钱买书的XX,UPUP.....),根据我们上面在设计销售经验来看,那出租我们应该怎么来设计呢?是不是也应该把不同的对象做为的独立的逻辑来处理呢?答案是肯定的,那到底要怎么去设计呢,这要求我们深入到具体的业务逻辑了。

通过分析现实中的业务逻辑,出租主要涉及到两个方面:租借和归还。而我们上面的设计中把顾客分为了会员和普通顾客两类,那么归还是不是应该划分为会员还书和普通顾客换书呢?这是肯定的,因为会员和普通顾客在租书的租金上是不一样的,会员和普通顾客在租金上应该是两种不同的策略。

从上面的分析得出,出租主要分为租借、会员归还和普通顾客归还这三种类型的逻辑。而租书不用给租金但必须先交押金,还会则需要收取租金(可从押金中扣除)。也就是说这三种类型里都回有处理出租(租借和归还)和交易金额的逻辑。既然都有共性,那也应该抽象出父类,是这样设计的吗?

 1 namespace EBook.Step3
 2 {
 3    /// <summary>
 4    /// 作为租赁业务的一个基类,所以的租赁行为都继承于它。
 5    /// </summary>

 6    public abstract class Hire
 7    {
 8        /// <summary>
 9        /// 处理租赁书的方法
10        /// </summary>

11        public abstract void Execute();
12
13        /// <summary>
14        /// 租书所得到的租金
15        /// </summary>

16        public abstract void GetMoney();
17    }

18}

我们来看看UML草图:

 1 /// <summary>
 2/// 租书
 3/// 分析:租书的时候是不需要支付租金的,但是需要支付押金
 4/// </summary>

 5 public  class Rent:Hire
 6 {
 7    /// <summary>
 8    /// 执行出租逻辑
 9    /// </summary>

10    public override void Execute()
11    {
12        Console.WriteLine("租出一本XXX书");
13    }

14
15    /// <summary>
16    /// 计算出租后所得到的租金
17    /// </summary>

18    public override void GetMoney()
19    {
20        Console.WriteLine("得到了XX.XX元的租金");
21    }

22}

 
 1 /// <summary>
 2/// 还书
 3/// 会员还书--租金和普通顾客的租金有区别
 4/// </summary>

 5 public  class MBack:Hire
 6 {
 7    /// <summary>
 8    /// 执行还书逻辑
 9    /// </summary>

10    public override void Execute()
11    {
12        Console.WriteLine("会员还书");
13    }

14
15    /// <summary>
16    /// 计算会员租书的租金
17    /// </summary>

18    public override void GetMoney()
19    {
20        Console.WriteLine("会员租金打5折");
21    }

22}

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

 4 public  class SBack:Hire
 5 {
 6    public override void Execute()
 7    {
 8        Console.WriteLine("普通顾客还书");
 9    }

10
11    /// <summary>
12    /// 计算普通顾客租书的租金
13    /// </summary>

14    public override void GetMoney()
15    {
16        Console.WriteLine("普通顾客租金打8折");
17    }

18}

此时,在Resolve类里就需要通过业务类型(销售或出租)、用户类型(会员或普通顾客)和出租类型(租借或归还)的不同层次的判断,然后去执行相应的具体逻辑实现。

 1 public  class Resolve
 2 {
 3    /// <summary>
 4    /// 
 5    /// </summary>
 6    /// <param name="sType">销售类型</param>
 7    /// <param name="uType">用户类型</param>
 8    /// <param name="rType">出租类型</param>

 9    public void Execute(string sType,string uType,string rType)
10    {
11        switch (sType)
12        {
13            case "销售"if (uType == "会员")
14                {
15                    Sell(new Buy());
16                }

17                else
18                {
19                    Sell(new SBuy());
20                }
break;
21            case "出租"if (rType == "租借")
22                {
23                    Hire(new Rent());
24                }

25                else
26                {
27                    if (uType == "会员") Hire(new MBack());
28                    else Hire(new SBack());
29                }
break;
30        }

31    }

32
33    private void Sell(Sell sell)
34    {
35        sell.Execute();
36        sell.GetMoney(); //本次交易的金额
37    }

38
39    private void Hire(Hire hire)
40    {
41        hire.Execute();
42        hire.GetMoney();
43    }

44}

可以看到,上面的Resolve类里的方法定义特别的复杂,switch和if....else语句使整个设计显得太过迂腐,破坏了设计之美。这里需要怎么改善代码,使得干净利落呢?这里我们先不谈使用switch和if......else造成的迂腐设计,在后续文章里我会详细的介绍怎么搞定这个坏点。

现在可以总结一下,从Resolve类的演变,我们可以得出这样一个结论:在调用类对象的属性和方法时,尽量避免将具体类对象作为传递参数,而应传递其抽象对象,更好地是传递接口,将实际的调用和具体对象完全剥离开,这样可以提高代码的灵活性。

如public void Sell(Sell sell){};方法,我们就是使用的高层抽象Sell(销售行为的父类)作为参数类型,而在实际调用中则传递的是具体的实现子类。这种遵循依赖于抽象而不依赖于具体实现的设计原则,让设计更具灵活性。上述设计的UML草图如下:

仔细观察会发现,Sell和Hire都具有相同的行为,这里我们完全可以在进一步的抽象,为这两个类定义一个统一的接口,详细本文就不做介绍,我已经把内容安排到下一篇文章里,大家可以关注本系列的后续文章。

本文主要目的是讲述OOP的设计过程演化,在实例上没有做详细的业务逻辑处理;我相信看到我这句话的朋友是认真的阅读完了本文的全部内容,通过对代码一步一步的修改重构,应用OOP的设计思想,抽象继承来演义OOP的设计过程,至于本文的价值所在,我想看过本文的朋友心里都有个数。


在我上篇文章应用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        #region 外露属性(用户名、书名、定价)
22
23        /// <summary>
24        /// 获取用户名称
25        /// </summary>

26        public string UserName
27        {
28            get return _userName; }
29        }

30
31        /// <summary>
32        /// 获取书名
33        /// </summary>

34        public string BookName
35        {
36            get return _bookName; }
37        }

38
39        /// <summary>
40        /// 获取书的定价
41        /// </summary>

42        public double BookPrice
43        {
44            get return _bookPrice; }
45        }

46
47        /// <summary>
48        /// 实际支付现金
49        /// </summary>

50        public double BookCash
51        {
52            get return _bookCash; }
53            set { _bookCash = value; }
54        }

55        #endregion

56
57        #region 实现基类中的抽象方法以及将任务分派到下面的派生类去
58        /// <summary>
59        /// 实现基类的抽象方法,但是考虑到还需要再下级的派生类来完成
60        /// 所以我们选择让他调用其他能够被派生类修改的方法
61        /// </summary>
62        /// <returns></returns>

63        public override string Execute()
64        {
65            return TExecute();
66        }

67
68        public override double GetMoney()
69        {
70            return TGetMoney();
71        }

72        #endregion
 
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,关于这点我将在后续文章里详细介绍,本文就介绍于此,我相信之前的设计过程演变+案例代码的展示+你自己学习后的总结会比我解说得更好。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值