策略模式概述:
策略模式(Pattern:Strategy)属于行为型模型,是指对一系列的算法定义,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
策略模式包含的角色及其职责:
- 抽象策略角色[Strategy]:策略类,定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,Context使用这个接口调用不同的算法,一般使用接口或抽象类实现。
- 具体策略类[ConcreteStrategy]:实现了Strategy定义的接口,包装了相关的算法和行为,提供具体的算法实现。
- 上下文角色[Context]:持有一个策略类的引用,最终给客户端调用。
- 需要使用ConcreteStrategy提供的算法。
- 内部维护一个Strategy的实例。
- 负责动态设置运行时Strategy具体的实现算法。
- 负责跟Strategy之间的交互和数据传递。
策略模式的设计原则及UML类图:
-
设计原则:变化的抽象成接口,面向接口编程而不是面向实现编程。
-
UML类图:
Java案例1:
-
抽象策略角色:
package StrategyPattern; /** * 现金收费抽象类 */ public abstract class CashSuper { //现金收取超类的抽象方法,收取现金,参数为原价,返回为当前价 public abstract double acceptCash(double money); }
-
具体策略类:
package StrategyPattern; /** * 正常收费子类,继承现金收费抽象类 */ public class CashNormal extends CashSuper { @Override public double acceptCash(double money) { //正常收费,返回原价 return money; } } package StrategyPattern; /** * 打折收费子类,继承现金收费抽象类 */ public class CashRebate extends CashSuper { private double moneyRebate = 1; public CashRebate(String moneyRebate) { //打折收费,初始化时,必须输入折扣率,如八折,就是0.8 this.moneyRebate = Double.valueOf(moneyRebate); } @Override public double acceptCash(double money) { return money*moneyRebate; } } package StrategyPattern; /** * 返利收费子类,继承现金收费抽象类 */ public class CashReturn extends CashSuper { private double moneyCondition = 0; //返利条件 private double moneyReturn = 0; //返利值 //返利收费,初始化时必须要输入返利条件和返利值,比如满300返100 public CashReturn(String moneyCondition, String moneyReturn) { this.moneyCondition = Double.valueOf(moneyCondition); this.moneyReturn = Double.valueOf(moneyReturn); } @Override public double acceptCash(double money) { if (money>=moneyCondition) { //若大于返利条件,则需要减去返利值 return money-Math.floor(money/moneyCondition)*moneyReturn; } return money; } }
-
上下文角色:策略与简单工厂结合
package StrategyPattern; /** * CashContext类 * 策略与简单工厂结合 */ public class CashContext { private CashSuper cs; //声明一个CashSuper对象 //通过构造方法,传入收费策略再生产具体的收费策略类 public CashContext(String rates) { switch (rates) { case "正常收费": cs = new CashNormal(); break; case "满1000减100": cs = new CashReturn("1000", "100"); break; case "打8折": cs = new CashRebate("0.8"); break; default: break; } } //根据收费策略的不同,获得计算结果 public double getResult(double money) { return cs.acceptCash(money); } }
-
客户端测试代码:
package StrategyPattern; /** * 客户端代码 */ public class Test1 { public static void main(String[] args) { int number = 300; //商品数量 int price = 10; //商品单价 Normal(number, price); //正常收费 Rebate(number, price); //打折收费 Return(number, price); //满减收费 } public static void Normal(int number, int price) { CashContext ccNormal = new CashContext("正常收费"); double result = ccNormal.getResult(number*price); System.out.println("正常收费:商品单价为:" + price + "元,商品数量为:" + number + "件,应付款:" + result + "元。"); } public static void Rebate(int number, int price) { CashContext ccRebate = new CashContext("打8折"); double result = ccRebate.getResult(number*price); System.out.println("打折收费:商品单价为:" + price + "元,商品数量为:" + number + "件,应付款:" + result + "元。"); } public static void Return(int number, int price) { CashContext ccReturn = new CashContext("满1000减100"); double result = ccReturn.getResult(number*price); System.out.println("打折收费:商品单价为:" + price + "元,商品数量为:" + number + "件,应付款:" + result + "元。"); } }
-
客户端测试代码打印结果:
Java案例2
求最小生成树有 Kruskal 和 Prim 两种算法;从长沙去哈尔滨可以乘坐火车或者飞机;在 Steam 购买游戏可以选择支付宝、微信或 visa 信用卡支付。这些都是同一个任务的多种实现方式。以 Steam 为例,支付的逻辑可能写成下面的形式:
class BuyGame {
void pay(String via) {
if ("Alipay".equals(via)) {
System.out.println("Pay with Alipay");
}
if ("Wechat".equals(via)) {
System.out.println("Pay with Wechat");
}
if ("Visa".equals(via)) {
System.out.println("Pay with visa card");
}
}
}
客户端通过参数传入支付方式:
public static void main(String[] args) {
BuyGame order = new BuyGame();
order.pay("Alipay");
}
看起来是解决了问题,但考虑一个情形:现在除了上述几种支付方式外,要添加 master 信用卡、银联、Paypal 支付。我们将不得不修改 BuyGame
类的代码,这违背了开闭原则。
策略模式可以解决这个问题。它的思想是基于单一责任:把算法实现与算法使用分离开,用若干个独立的类(策略类)专门实现算法。算法的调用者(称为“环境类”)在执行任务时,委派一个策略类来完成任务。
按照策略模式,首先需要把支付方式分拆到自己的类里面:
interface PaymentStrategy {
void pay();
}
class AlipayStrategy implements PaymentStrategy {
public void pay() {
System.out.println("Pay with Alipay");
}
}
class WechatStrategy implements PaymentStrategy {
public void pay() {
System.out.println("Pay with Wechat");
}
}
class VisaStrategy implements PaymentStrategy {
public void pay() {
System.out.println("Pay with visa card");
}
}
BuyGame
委派一个支付策略来实现支付逻辑:
class BuyGame {
private PaymentStrategy via;
public BuyGame(PaymentStrategy via) {
this.via = via;
}
public void pay() {
via.pay();
}
}
现在,客户想通过 Alipay 来支付一笔订单,只需要把一个 AlipayStrategy
实例交给 BuyGame,再调用 pay()
接口:
public static void main(String[] args) {
BuyGame order = new BuyGame(new AlipayStrategy());
order.pay();
}
很明显,想要添加新的支付方式,只需要添加几个对 PaymentStrategy
的实现类即可。无需改动其他部分的代码;client 代码也无需改变,完美地实现了开闭原则。
策略模式的优缺点及使用场景:
优点
- 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,从而避免重复的代码。
- 策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
- 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
缺点
-
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
-
策略模式造成很多的策略类,每个具体策略类都会产生一个新类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。
应用场景
- 多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。
- 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。
- 对客户隐藏具体策略(算法)的实现细节,彼此完全独立。