您的位置:首页 > 其它

【设计模式】策略模式——以商场促销为例

2015-12-04 14:25 477 查看
本文内容参考自《大话设计模式》(程杰 著)

注:以下代码为java实现

版本1

需求:

做一个商场收银软件,营业员根据客户所购买商品的单价和数量,向客户收费。

关键代码:

public class Cash
{
private double  total   = 0;

public void submit(int num, double price)
{
double totalPrices = num * price;
total += totalPrices;

System.out.println("单价:" + price + " 数量:" + num + "合计:" + totalPrices);
}

public double getTotal()
{
return total;
}

public void setTotal(double total)
{
this.total = total;
}
}


版本2

需求:

增加打折功能

思路1:修改代码,比如打7折,则total *= 0.7;

评价:如果取消打折,或者修改折扣,需要频繁修改代码,不推荐。

思路2:增加折扣选项,关键代码如下:

public class Cash
{
private double  total           = 0;
private int     selectedIndex   = 0;

public void selectFormLoad()
{
String[] selectForm = { "正常收费", "打8折", "打7折", "打5折" };
selectedIndex = 0;
}

public void submit(int num, double price)
{
double totalPrices = 0;
switch (selectedIndex)
{
case 0:
totalPrices = num * price;
break;
case 1:
totalPrices = num * price * 0.8;
break;
case 2:
totalPrices = num * price * 0.7;
break;
case 3:
totalPrices = num * price * 0.5;
break;
}
total += totalPrices;
System.out.println("单价:" + price + " 数量:" + num + "合计:" + totalPrices);
}

public double getTotal()
{
return total;
}

public void setTotal(double total)
{
this.total = total;
}

public int getSelectedIndex()
{
return selectedIndex;
}

public void setSelectedIndex(int selectedIndex)
{
this.selectedIndex = selectedIndex;
}
}


问题:重复的代码太多,而且选项少,可变性不高!

版本3

需求:

可以灵活修改折扣,并且可以返利

//现金收费接口
public interface CashSuper
{
public double acceptCash(double money);
}
//正常收费子类
public class CashNormal implements CashSuper
{

public double acceptCash(double money)
{
return money;
}
}
//打折收费子类
public class CashRebate implements CashSuper
{
private double  moneyRebate = 1;

public CashRebate(double moneyRebate)
{
this.moneyRebate = moneyRebate;
}

public double acceptCash(double money)
{
return money * moneyRebate;
}
}
//返利收费子类
public class CashReturn implements CashSuper
{
private double  moneyCondition  = 0;
private double  moneyReturn     = 0;

public CashReturn(double moneyCondition, double moneyReturn)
{
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn;
}

public double acceptCash(double money)
{
double result = money;
if (money >= moneyCondition)
{
result = money - money / moneyCondition * moneyReturn;
}
return result;
}
}
//现金收费工厂类
public class CashFactory
{
public static CashSuper createCash(String type)
{
CashSuper cs = null;
if ("正常收费".equals(type))
{
cs = new CashNormal();
}
else if ("满300返100".equals(type))
{
cs = new CashReturn(300, 100);
}
else if ("打8折".equals(type))
{
cs = new CashRebate(0.8);
}

return cs;
}
}
//客户端代码
public class Main
{
private static double   total   = 0;

public static void main(String[] args)
{
consume("正常收费", 1, 1000);
consume("满300返100", 1, 1000);
consume("打8折", 1, 1000);

System.out.println("总计:" + total);
}

public static void consume(String type, int num, double price)
{
CashSuper csuper = CashFactory.createCash(type);
double totalPrices = 0;
totalPrices = csuper.acceptCash(num * price);
total += totalPrices;
System.out.println("单价:" + price + " 数量:" + num + "合计:" + totalPrices);
}
}


以上代码进行了抽象封装,并使用了简单的工厂类,灵活性高了很多。

问题:简单工厂模式只是解决对象的创建问题,而且由于工厂本身包括了所有的收费方式,商场是可能经常性地更改打折额度和返利额度的,每次维护或扩展收费方式都要改动这个工厂,以致代码需要重新编译部署,这是非常糟糕的处理方式,所以用它不是最好的办法。面对算法的时常变动,我们可以使用策略模式!

版本4

需求:

可以经常性地更改打折额度和返利额度,而且要维护成本较低。

策略模式(Strategy):

它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。

商场收银时,如何促销,用打折还是返利,其实都是一些算法,最重要的是这些算法是随时都可能互相替换的,就这点变化,而封装变化点是我们面向对象的一种很重要的思维方式。我们来看看策略模式的结构图和基本代码:



//Strategy类,定义所有支持的算法的公共接口
public interface Strategy
{
public void algorithmInterface();
}
//ConcreteStrategy封装了具体的算法或行为,继承于Strategy
public class ConcreteStrategyA implements Strategy
{
public void algorithmInterface()
{
System.out.println("算法A实现");
}
}
public class ConcreteStrategyB implements Strategy
{
public void algorithmInterface()
{
System.out.println("算法A实现");
}
}
public class ConcreteStrategyC implements Strategy
{
public void algorithmInterface()
{
System.out.println("算法C实现");
}
}
//Context用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用
public class Context
{
private Strategy    strategy;

public Context(Strategy strategy)
{
this.strategy = strategy;
}

public void contextInterface()
{
strategy.algorithmInterface();
}
}
//客户端代码
public class Main
{
public static void main(String[] args)
{
Context context;
context = new Context(new ConcreteStrategyA());
context.contextInterface();

context = new Context(new ConcreteStrategyB());
context.contextInterface();

context = new Context(new ConcreteStrategyC());
context.contextInterface();
}
}


所以我们的可以进行以下修改:



//CashContext类
public class CashContext
{
CashSuper   cashSuper;

public CashContext(CashSuper cashSuper)
{
this.cashSuper = cashSuper;
}

public double acceptCash(double money)
{
return cashSuper.acceptCash(money);
}
}
//客户端代码
public class Main
{
private static double   total   = 0;

public static void main(String[] args)
{
consume("正常收费", 1, 1000);
consume("满300返100", 1, 1000);
consume("打8折", 1, 1000);

System.out.println("总计:" + total);
}

public static void consume(String type, int num, double price)
{
CashContext cashContext = null;

if ("正常收费".equals(type))
{
cashContext = new CashContext(new CashNormal());
}
else if ("满300返100".equals(type))
{
cashContext = new CashContext(new CashReturn(300, 100));
}
else if ("打8折".equals(type))
{
cashContext = new CashContext(new CashRebate(0.8));
}

double totalPrices = cashContext.acceptCash(num * price);
total += totalPrices;

System.out.println("单价:" + price + " 数量:" + num + "合计:" + totalPrices);
}
}


问题:缺乏工厂模式的优势,在客户端要进行判断。

版本5

将策略模式与简单工厂模式结合起来:

//改造后的CashContext
public class CashContext
{
CashSuper   cashSuper;

public CashContext(CashSuper cashSuper)
{
this.cashSuper = cashSuper;
}

public CashContext(String type)
{
if ("正常收费".equals(type))
{
cashSuper = new CashNormal();
}
else if ("满300返100".equals(type))
{
cashSuper = new CashReturn(300, 100);
}
else if ("打8折".equals(type))
{
cashSuper = new CashRebate(0.8);
}
}

public double acceptCash(double money)
{
return cashSuper.acceptCash(money);
}
}
//客户端代码
public class Main
{
private static double   total   = 0;

public static void main(String[] args)
{
consume("正常收费", 1, 1000);
consume("满300返100", 1, 1000);
consume("打8折", 1, 1000);

System.out.println("总计:" + total);
}

public static void consume(String type, int num, double price)
{
CashContext cashContext = new CashContext(type);

double totalPrices = cashContext.acceptCash(num * price);
total += totalPrices;

System.out.println("单价:" + price + " 数量:" + num + "合计:" + totalPrices);
}
}


简单工厂 和 策略模式+简单工厂 的对比

客户端代码:

//简单工厂模式的用法
CashSuper csuper = CashFactory.createCash(type);
...
totalPrices = csuper.acceptCash(num * price);

//策略模式与简单工厂模式结合的用法
CashContext cashContext = new CashContext(type);
double totalPrices = cashContext.acceptCash(num * price);


简单工厂模式需要让客户端认识两个类,CashSuper和CashFactory,而策略模式与简单工厂模式结合的用法,客户端就只需要认识一个类CashContext。耦合度更加降低。

我们在客户端实例化的是CashContext的对象,调用的是CashContext的方法,这使得具体的收费算法彻底地与客户端分离。连算法的父类CashSuper都不让客户端认识了。相当于创建了一个句柄类。

总结

策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法与使用算法之间的耦合。

另外,它简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。

遗留问题:如果我们需要增加一种算法,比如满200返50,你就必须要改CashContext中的if或switch代码,有没有更低的维护成本?

(使用反射)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: