本文内容参考自《大话设计模式》(程杰 著)
注:以下代码为java实现
做一个商场收银软件,营业员根据客户所购买商品的单价和数量,向客户收费。
关键代码:
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;
}
}
增加打折功能
思路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;
}
}
问题:重复的代码太多,而且选项少,可变性不高!
可以灵活修改折扣,并且可以返利
//现金收费接口
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);
}
}
以上代码进行了抽象封装,并使用了简单的工厂类,灵活性高了很多。
问题:简单工厂模式只是解决对象的创建问题,而且由于工厂本身包括了所有的收费方式,商场是可能经常性地更改打折额度和返利额度的,每次维护或扩展收费方式都要改动这个工厂,以致代码需要重新编译部署,这是非常糟糕的处理方式,所以用它不是最好的办法。面对算法的时常变动,我们可以使用策略模式!
可以经常性地更改打折额度和返利额度,而且要维护成本较低。
它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
商场收银时,如何促销,用打折还是返利,其实都是一些算法,最重要的是这些算法是随时都可能互相替换的,就这点变化,而封装变化点是我们面向对象的一种很重要的思维方式。我们来看看策略模式的结构图和基本代码:
//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);
}
}
问题:缺乏工厂模式的优势,在客户端要进行判断。
将策略模式与简单工厂模式结合起来:
//改造后的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代码,有没有更低的维护成本?
(使用反射)