在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等,超市促销可以釆用打折、送商品、送积分等方法。
在软件开发中,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等。
如果使用多重条件转移语句实现(即硬编码),不但使条件语句变得很复杂,而且增加、删除或更换算法要修改原代码,不易维护,违背开闭原则。如果采用策略模式就能很好解决该问题。
一、策略模式
1、策略模式的定义
策略(Strategy Pattern)模式,又叫作政策模式(P玻璃窗与Pattern):
该模式定义了一系列算法,并将每个算法分别封装起来,让它们之间可以相互替换,从而让算法的变化不会影响使用算法的客户。
策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式使用的是面向对象的继承和多态机制,从而实现同一行为在不同的场景下具备不同的实现。
2、策略模式的结构
策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性,现在我们来分析其基本结构和实现方法。
(1)模式的结构
主要角色如下:
- 抽象策略角色(Strategy):定义策略或者算法的行为。各种不同的算法以不同的方式实现这个接口,上下文角色使用这个接口调用不同的算法,一般是一个公共接口或抽象类。
- 具体策略角色(Concrete Strategy):实现了抽象策略接口,提供具体的策略或者算法实现。
- 上下文角色(Context):持有一个策略类的引用,最终给客户端调用。
上下文角色的职责是:隔离客户端与策略类的耦合,让客户端完全与上下文角色访问,客户端不需要关心具体策略。
(2)结构图如下:(图来自网络)
3、优缺点
主要优点如下:
- 避免使用多重条件转移语句不,如 if…else 语句、switch…case 语句。
- 策略模式符合开闭原则。
- 使用策略模式可以提高算法的保密性和安全性。
主要缺点如下:
- 客户端必须理解所有的策略或者算法,并且可以自行决定使用哪一个策略类。
- 策略模式造成很多的策略类,增加维护难度。
4、使用场景
- 针对同一类型问题,有多种处理方式,每一种都能独立解决问题
- 需要*切换算法的场景
- 需要屏蔽算法规则的场景
二、模式的通用实现
代码如下:
public class StrategyPattern {
public static void main(String[] args) {
Context c = new Context();
Strategy s = new ConcreteStrategyA();
c.setStrategy(s);
c.strategyMethod();
System.out.println("-----------------");
s = new ConcreteStrategyB();
c.setStrategy(s);
c.strategyMethod();
}
}
//抽象策略类
interface Strategy {
void strategyMethod(); //策略方法
}
//具体策略类A
class ConcreteStrategyA implements Strategy {
@Override
public void strategyMethod() {
System.out.println("具体策略A的策略方法被访问!");
}
}
//具体策略类B
class ConcreteStrategyB implements Strategy {
@Override
public void strategyMethod() {
System.out.println("具体策略B的策略方法被访问!");
}
}
//上下文类
class Context {
private Strategy strategy;
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void strategyMethod() {
strategy.strategyMethod();
}
}
三、模式的应用实例
策略模式与单例模式或者工厂模式等其他模式使用功能,更提高代码的健壮性,优化代码。
这里用订单支付场景为例。
1、支付状态信息的包装类
public class MsgResult {
private int code;
private Object data;
private String msg;
public MsgResult(int code, Object data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}
@Override
public String toString() {
return "MsgResult{" +
"code=" + code +
", data=" + data +
", msg='" + msg + '\'' +
'}';
}
2、抽象策略类:支付渠道
public abstract class Payment {
// 获取支付方式的名字
public abstract String getName();
// 查询余额
public abstract double queryBalance(String uid);
// 通用的支付逻辑方法,这里简单点。具体的支付方法可以写个钩子方法,放到子类去实现。
public MsgResult pay(String uid, double amount){
if(queryBalance(uid) < amount){
return new MsgResult(500, "支付失败","余额不足");
}
return new MsgResult(200, "支付成功","余额金额" + amount);
}
}
3、具体策略类:阿里支付等
4、上下文角色:支付策略管理类
public class PayStrategy {
private static Map<String, Payment> strategy = new HashMap<>();
// 初始化映射关系
static {
strategy.put(PayKey.ALI_PAY, new AliPay());
strategy.put(PayKey.JD_PAY, new JDPay());
strategy.put(PayKey.WECAHT_PAY, new WechatPay());
strategy.put(PayKey.DEFAULT_PAY, new AliPay());
}
// 对外提供获取某个策略的方法
public static Payment get(String payKey){
// 不包含,则返回默认的
if(!strategy.containsKey(payKey)){
return strategy.get(PayKey.DEFAULT_PAY);
}
return strategy.get(payKey);
}
}
interface PayKey{
String ALI_PAY = "AliPay";
String JD_PAY = "JdPay";
String WECAHT_PAY = "WechatPay";
String DEFAULT_PAY = "AliPay";
5、订单类
public class Order {
private String uid;
private String orderId;
private double amount;
public Order(String uid, String orderId, double amount) {
this.uid = uid;
this.orderId = orderId;
this.amount = amount;
}
public MsgResult pay(){
return pay(PayKey.DEFAULT_PAY); //默认支付
}
// 订单支付
public MsgResult pay(String payKey){
Payment payment = PayStrategy.get(payKey);
System.out.println("欢迎使用" + payment.getName());
System.out.println("本次交易金额为" + amount + ",开始扣款");
return payment.pay(uid, amount);
}
6、测试下单支付
参考文章:
—— Stay Hungry. Stay Foolish. 求知若饥,虚心若愚。