一、介绍
1、问题抛出:现在有如下需求:商场收银软件,营业员根据客户所购买商品的单价可数量,向客户收费,商场可能会有各种各样的促销活动(如:打9折、打8折、满300减50、满500减100等)。
如果用简单工厂模式实现:首先活动种类可能有很多(假如老板每天都很开心,每天都变着花样去搞活动),所以考虑各种促销活动之间的解耦,和方便扩展,要合理的封装业务类(面向对象的编程,并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。)并结合简单工厂模式,实现如下:
(1)收费的抽象类
public abstract class CashSuper
{
///
/// 根据总金额计算优惠后的金额
///
///
///
public abstract double AcceptCash(double money);
}
(2)收费方式:
正常收费
public class CashNormal : CashSuper
{
public override double AcceptCash(double money)
{
// 正常收费
return money;
}
}
打折收费
public class CashRebate : CashSuper
{
private double rebate = 1d;
public CashRebate(double rebate)
{
this.rebate = rebate;
}
public override double AcceptCash(double money)
{
return money * this.rebate;
}
}
满减返利收费
public class CashReduction : CashSuper
{
private double moneyCondition = 0d;
private double moneyReduction = 0d;
public CashReduction(double moneyCondition, double moneyReduction)
{
this.moneyCondition = moneyCondition; // 满减的条件
this.moneyReduction = moneyReduction; // 满减的金额
}
public override double AcceptCash(double money)
{
double reduction = Math.Floor(money % this.moneyCondition) * this.moneyReduction;
return money - reduction;
}
}
(3)收费工厂
public class CashFactory
{
public static CashSuper CreateCashAccept(string type)
{
CashSuper cashSuper = null;
switch (type)
{
case "正常收费":
cashSuper = new CashNormal();
break;
case "打八折":
cashSuper = new CashRebate(0.8);
break;
case "满300减50":
cashSuper = new CashReduction(300, 50);
break;
}
return cashSuper;
}
}
(4)客户端
CashSuper cashSuper = CashFactory.CreateCashAccept("打八折");
double moneny = cashSuper.AcceptCash(500);
实现上毫无问题,但是由于工厂本身包括所有的收费方式,商场可能经常性地更改打折额度和返利额度,每次维护或扩展收费方式时都要改动这个工厂,以致于代码需要重新编译部署,所有这里用简单工厂模式不是最好的办法。
2、策略模式引入:简单工厂模式只是解决对象的创建问题,面对算法的经常变化,应该有更好的办法。策略模式(Strategy):它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
在上面例子中,商场选择怎样的促销活动,其实都是一些算法,用工厂来生成算法对象,这没有错,但算法本身只是一种策略,而这些算法是随时都可能互相替换的,这就是变化点,而封装变化点是面向对象的一种很重要的思维方式。下面是策略模式的结构图:
策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同。它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。策略模式的 Strategy 类层次为 Context 定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。 如上面的打折、返利等其他算法其实都是对商品收费的一种计算方式,通过继承,可以得到它们的公共功能获得计算费用的结果 GetFinalCash,这使得算法间用了抽象的父类 CashSuper。
策略模式同样简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
**策略模式封装了变化。**当不同的行为堆砌在一个类中时,很难避免使用条件语句来选择合适的行为。将这些行为封装在一个独立的 Strategy 类中,可以在使用这些行为的类中消除条件语句。策略模式就是用来封装算法的,在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的 Context 对象。这本身并没有解除客户端需要选择判断的压力,而策略模式与简单工厂模式结合后,选择具体实现的职责也可以由 Context 来承担,最大化地减轻类客户端的职责。但是它依然不是完美的实现方式,因为在 CashContext 里面同样用到了 switch 选择,如果需要增加一种算法就要改 switch 代码。这里可以通过反射的技术动态的创建收费策略对象。
3、比较:简单工厂模式和策略模式比较
相同点:在结构模式上两者很相似
(1)简单工厂模式:客户端传一个条件进工厂类,工厂类根据条件生成相应的对象并返回给客户端;
策略模式:客户端创建一个Context对象a,创建策略对象并当做参数传递给a,然后客户端使用a通过某种方法得到想要的值返回给客户端。
差异性:
(1) 它们的用途不一样。简单工厂模式是创建型模式,它的作用是创建对象。策略模式是行为型模式,作用是在许多行为中选择一种行为,关注的是行为的多样性。
(2)解决的问题不同。简单工厂模式是解决资源的统一分发,将对象的创立同客户端分离开来。策略模式是为了解决策略的切换和扩展。
(3)工厂相当于黑盒子,策略相当于白盒子。如果在适合用策略模式的情况下用简单工厂模式,如果新增加策略就要修改工厂类,而这个可能会导致其他错误和比较繁琐,而如果使用了策略模式,只要将新增加的策略当做参数传递到Context类中即可。
4、策略模式应用:
在前面收费抽象类和收费实现类的基础上,
(1)定义一个 CashContext 类
public class CashContext
{
private CashSuper cashSuper = null; // 声明一个 CashSuper 对象
///
/// 通过构造函数传入具体收费策略
///
///
public CashContext(CashSuper cashSuper)
{
this.cashSuper = cashSuper;
}
///
/// 获取最终的金额
///
///
///
public double GetFinalCash(double money)
{
return this.cashSuper.AcceptCash(money);
}
}
(2)客户端代码
CashContext context = null;
switch (type)
{
case "正常收费":
context = new CashContext(new CashNormal());
break;
case "打八折":
context = new CashContext(new CashRebate(0.8));
break;
case "满300减50":
context = new CashContext(new CashReduction(300, 50));
break;
}
double moneny = context.GetFinalCash(500);
5、策略模式+简单工厂模式:在上面客户端中要判断选择的收费策略,然后创建选择的收费策略对象,这里就导致客户端要知道所以的收费策略,耦合严重。但是既然是创建对象,而且创建的是同一种抽象类型(CashSuper)的对象,这样又可以加入工厂模式。
对 CashContext 构造函数改造如下:传入的不再是具体策略,而是收费的类型,在构造函数中根据收费类型创建具体收费策略(简单工厂)
///
/// 传入收费类型,在内部创建具体的收费策略对象
///
///
public CashContext(string type)
{
switch (type)
{
case "正常收费":
cashSuper = new CashNormal();
break;
case "打八折":
cashSuper = new CashRebate(0.8);
break;
case "满300减50":
cashSuper = new CashReduction(300, 50);
break;
}
}
客户端
CashContext context = new CashContext("打八折");
double moneny = context.GetFinalCash(500);
对比简单工厂模式客户端代码
CashSuper cashSuper = CashFactory.CreateCashAccept("打八折");
double moneny = cashSuper.AcceptCash(500);
发现简单工厂模式中客户端需要知道两个类和两个方法,一个收费对象抽象类 CashSuper 和计费的方法 AcceptCash,一个对象工厂的类 CashFactory 和一个创建具体收费对象的方法 CreateCashAccept。而在策略模式 + 简单工厂模式中,客户端只需要知道一个收费的上下文对象 CashContext 和得到最终收费金额的方法 GetFinalCash 即可,显而易见,降低了业务和逻辑和客户端的耦合程度,也易于扩展。
二、应用:
1、使用场景:
(1)如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
(2)一个系统需要动态地在几种算法中选择一种。
(3)如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
2、应用实例:
(1)旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
(2)JAVA AWT 中的 LayoutManager。