面向对象的编程并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。
2.3 简单工厂实现
现金收费抽象类:
public abstract class CashSuper { //现金收取超类的抽象方法,收取现金,参数为原价,返回当前价 public abstract double AcceptCash(double money); }
正常收费子类:
class CashNormal : CashSuper { public override double AcceptCash(double money) { return money; } }
打折收费子类:
class CashRebate : CashSuper { private double moneyRebate = 1d;
//打折收费,初始化时,必须要输入折扣率,如打8折,就是0.8 public CashRebate(string moneyRebate) { this.moneyRebate = double.Parse(moneyRebate); } public override double AcceptCash(double money) { return money * moneyRebate; } }
返利收费子类:
class CashReturn :CashSuper { private double moneyCondition = 0.0d; private double moneyReturn = 0.0d; //返利收费,初始化时必须要输入返利条件和返利值,比如满300返100 public CashReturn(string moneyCondition,string moneyReturn) { this.moneyCondition = double.Parse(moneyCondition); this.moneyReturn = double.Parse(moneyReturn); } public override double AcceptCash(double money) { double result = money; if (money >= moneyCondition) //若大于返利条件,则需要减去返利值 result = money - Math.Floor(money / moneyCondition) * moneyReturn; return result; } }
现金收费工厂类:(收费对象生成工厂)
class CashFactory { public static CashSuper CreateCashAccept(string type) { CashSuper cs = null; switch(type) { case "正常收费": //根据条件返回对应的对象 cs = new CashNormal(); break; case "满300返100": CashReturn cr1 = new CashReturn("300", "100"); cs = cr1; break; case "打8折": CashRebate cr2 = new CashRebate("0.8"); cs = cr2; break; } return cs; } }
客户端程序关键代码:
CashSuper csuper = CashFactory.CreateCashAccept("正常收费");
简单工厂模式只是解决对象的创建问题,虽然工厂可能已经包括了所有的收费方式,但商场可能经常性地更改打折额度和返利额度,每次维护或扩展收费方式都要
改动这个工厂,以致代码需要重新编译部署,这真的是很糟糕的处理方式,所以用它部署最好的办法。
面对算法的时常变动,还有更适合的设计模式。(策略模式)
2.4 策略模式
策略模式定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
商场收银时如何促销,用打折还是返利,其实都是一些算法,用工厂来生成算法对象,这没有错,但算法本身只是一种策略,最重要的是这些算法是随时都可能互相替换的,
这就是变化点,而封装变化点是我们面向对象的一种重要的思维方式。
抽象算法类:(定义所有支持的算法的公共接口)
abstract class Strategy { //算法方法 public abstract void AlgorithmInterface(); }
具体算法A:(封装了具体的算法或行为,继承于抽象算法类)
class ConcreteStrategyA : Strategy { //算法A实现方法 public override void AlgorithmInterface() { Console.WriteLine("算法A实现"); } }
具体算法B:
class ConcreteStrategyB :Strategy { public override void AlgorithmInterface() { Console.WriteLine("算法B实现"); } }
上下文类:(用一个具体的算法对象来配置,维护一个对 Strategy 对象的引用)
class Content { Strategy strategy; public Content(Strategy strategy) //通过构造方法,传入具体的策略对象 { this.strategy = strategy; } //上下文接口,根据具体的策略对象,调用其算法的方法 public void ContentInterface() { strategy.AlgorithmInterface(); } }
2.5 策略模式实现
现金收费抽象类和三个具体的策略类不需要更改。
CashContent 类:
class CashContent { private CashSuper cs; //声明一个 CashSuper对象 public CashContent(CashSuper csuper) //通过构造方法,传入具体的收费策略 { cs = csuper; } public double GetResult(double money) { return cs.AcceptCash(money); //根据收费策略的不同,获得计算结果 } }
根据目前的代码,仍需要在客户端判断用哪一种算法(即 switch 语句仍需要写在客户端代码里),应该想一个办法把判断从客户端程序中移走。
放在客户端和放在其他类的区别在于:当在多处调用时维护(修改)起来更方便,同时也更容易扩展和移植到其他程序上去。
简单工厂模式与策略模式的对比:
在简单工厂模式中,工厂类中只有一个返回需要实例化的对象的方法。
在策略模式中,上下文类包含三部分:
第一部分是一个类型为抽象算法类的字段(修饰符可以为默认的友好型),
第二部分是上下文类的构造函数,初始化时需要传入具体的策略对象,
第三部分是上下文接口(一个根据具体的策略对象,调用其算法的方法)。
2.6 策略与简单工厂结合
改造后的 CashContent:
class CashContent { private CashSuper cs; //声明一个 CashSuper 对象 public CashContent(string type) //注意参数不是具体的收费策略对象,而是一个字符串,表示收费类型 { switch (type) { case "正常收费": CashNormal cs0 = new CashNormal(); cs = cs0; break; case "满300返100": CashReturn cr1 = new CashReturn("300", "100"); cs = cr1; break; case "打8折": CashRebate cr2 = new CashRebate("0.8"); cs = cr2; //将实例化具体策略的过程由客户端转移到 Content 类中,这是简单工厂的应用 break; } } public double GetResult(double money) { return cs.AcceptCash(money); //根据收费策略的不同,获得计算结果 } }
使用改造后的上下文类,客户端代码变得简单明了了:
// 简单工厂模式的用法:(客户端需要认识两个类,CashSuper 和 CashFactory)
CashSuper csuper = CashFactory.CreateCashAccept("正常收费");
// 策略模式与简单工厂结合的用法:(客户端只需要认识一个类 CashContent 就可以了,耦合度更低了)—— 只要提到“客户端”,其实就是调用的地方
CashContent csuper = new CashContent("正常收费");
2.7 策略模式解析
策略模式是用来封装算法的,但在实践中我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到“需要在不同时间应用不同的业务规则”,就可以考虑使用策略模式处理这种变化的可能性。
在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的 Content 对象(CashContent cc = null;…… cc = new CashContent(new CashNormal());)。这本身并没有解除客户端需要选择判断的压力,
而策略模式与简单工厂模式结合后,选择具体实现的职责也可以由 Content 来承担,这就最大化地减轻了客户端的职责。(换句话说,当在多处进行调用时就变得更加简单了)
这已经比起初的策略模式好用了,但它还不够完美,因为在 CashContent 里还是用到了 switch,也就是说,如果我们需要增加一种算法,比如“满200送50”,你就必须要更改 CashContent 中的 switch 代码,
这总还是让人很不爽。更好的办法就是使用反射技术来解决。