策略模式
策略模式的定义
策略模式(Strategy attern)是指,定义了算法家族、分别封装起来,让他们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的用户。
避免多重分支语句 if...else... 和 switch语句
应用场景 - 优惠打折
1.加入系统中有很多类,而他们的区别仅仅在于他们的行为不同。
2.一个系统需要动态地在集中算法中选择一种。
策略模式是返回一个策略类,再由策略类决定做什么操作,而委派模式则是将一个任务全权委派出去,只看结果,这也是策略模式和委派模式的区别之一
例如,一个商品优惠打折的例子
打折活动的规范接口
/**
* 所有打折活动的规范接口
*/
public interface IPromotionStrategy {
void doPromotion();
}
有以下几种打折策略
/**
* 优惠打折策略-返现策略
*/
public class CashBackStrategy implements IPromotionStrategy {
@Override
public void doPromotion() {
System.out.println("付款成功之后,返现到用户账号余额");
}
}
/**
* 优惠打折策略-优惠券抵扣
*/
public class CouponStrategy implements IPromotionStrategy {
@Override
public void doPromotion() {
System.out.println("领用优惠券,直接抵扣商品费用");
}
}
/**
* 优惠打折策略-拼团策略
*/
public class GroupByStrategy implements IPromotionStrategy {
@Override
public void doPromotion() {
System.out.println("拼团,满20人成团,全团享受8折优惠");
}
}
/**
* 优惠打折策略-不参与优惠打折
* 不参加优惠
*/
public class EmptyPromotionStrategy implements IPromotionStrategy{
@Override
public void doPromotion() {
System.out.println("不参加优惠");
}
}
接下来,我们需要一个活动类,来对策略类进行管理
/**
* 优惠活动处理类
*/
public class PromotionActivity {
IPromotionStrategy promotionStrategy;
public PromotionActivity(IPromotionStrategy promotionStrategy) {
this.promotionStrategy = promotionStrategy;
}
public void execute() {
this.promotionStrategy.doPromotion();
}
}
当用户进行买单时,选择要使用的优惠方式,因此,会有一个用户选择优惠方式,得到打折策略的过程,此过程就需要使用到策略
因为每个打折策略,不会因为用户或商品变化而变化,因此这里可以结合单例,和工厂模式,根据用户选择的打折优惠策略,来生产一个打折优惠策略类
首先,我们创建一个选择策略的工厂类
工厂类-1私有构造方法
私有构造方法,并给出一个全局获取对象的方法接口
public class PromotionStrategyFactory {
private PromotionStrategyFactory() {};
/** 工厂 */
public static IPromotionStrategy getPromotionStrategy(String promotionKey) {
//TODO 待实现
}
}
构建打折优惠策略KEY
将现有的打折优惠策略KEY构建成一个枚举或是常亮,这里用interface
构建成常量
private interface PromotionKey {
String COUPON = "COUPON";
String CASHBACK = "BASHBACK";
String GROUP_BY = "GROUP_BY";
}
注册式单例
在工厂类中添加静态变量 PROMOTION_STRATEGY_MAP
并用注册式单例的方式,将现有的打折优惠策略注册到 工厂变量
private static Map<String, IPromotionStrategy> PROMOTION_STRATEGY_MAP = new HashMap<>();
static { //注册式单例 结合策略的KEY,对应到每个kEY的策略实现
PROMOTION_STRATEGY_MAP.put(PromotionKey.COUPON, new CouponStrategy());
PROMOTION_STRATEGY_MAP.put(PromotionKey.CASHBACK, new CashBackStrategy());
PROMOTION_STRATEGY_MAP.put(PromotionKey.GROUP_BY, new GroupByStrategy());
}
策略实现
在工厂提供的全局获取对象方法中实现策略
/** 用户未选择打折优惠策略时,不参与优惠策略的默认变量 */
private static final IPromotionStrategy NON_PROMOTION = new EmptyPromotionStrategy();
/** 工厂 */
public static IPromotionStrategy getPromotionStrategy(String promotionKey) {
//策略
IPromotionStrategy iPromotionStrategy = PROMOTION_STRATEGY_MAP.get(promotionKey);
return iPromotionStrategy == null ? NON_PROMOTION : iPromotionStrategy;
}
由于PROMOTION_STRATEGY_MAP
中将系统已有的打折优惠策略都注册进去了,此处,根据用户传入的策略KEy,获取到一个策略,并返回
策略工厂完整代码
/**
* 优惠策略的工厂
*/
public class PromotionStrategyFactory {
private PromotionStrategyFactory() {};
private static Map<String, IPromotionStrategy> PROMOTION_STRATEGY_MAP = new HashMap<>();
static { //注册式单例
PROMOTION_STRATEGY_MAP.put(PromotionKey.COUPON, new CouponStrategy());
PROMOTION_STRATEGY_MAP.put(PromotionKey.CASHBACK, new CashBackStrategy());
PROMOTION_STRATEGY_MAP.put(PromotionKey.GROUP_BY, new GroupByStrategy());
}
private static final IPromotionStrategy NON_PROMOTION = new EmptyPromotionStrategy();
/** 工厂 */
public static IPromotionStrategy getPromotionStrategy(String promotionKey) {
//策略
IPromotionStrategy iPromotionStrategy = PROMOTION_STRATEGY_MAP.get(promotionKey);
return iPromotionStrategy == null ? NON_PROMOTION : iPromotionStrategy;
}
private interface PromotionKey {
String COUPON = "COUPON";
String CASHBACK = "BASHBACK";
String GROUP_BY = "GROUP_BY";
}
}
测试-优惠打折
public static void main(String[] args) {
//优化之后
String promotionKey = PromotionStrategyFactory.PromotionKey.GROUP_BY;
/** 根据优惠类型,调用优惠类型工厂以及中的策略 */
PromotionActivity activity = new PromotionActivity(PromotionStrategyFactory
.getPromotionStrategy(promotionKey));
activity.execute();
}
首先,使用策略工厂,按照用户选择的优惠打折策略,获取到具体的优惠打折策略,然后在执行优惠打折方法execute();
应用场景-支付
通常,我们在用餐时,会扫描商家给出的二维码,商家的二维码是固定的,我们既可以用支付宝、微信、京东金融、以及云闪付支付(假设上述商家都开通了支付)那么当我们使用对应的APP扫码之后,发起支付时,支付的系统服务方会根据用户扫码客户单的APP返回的对应TYPE决定是那种支付,然后在决定使用哪种支付的实现,来完成本次支付,这就是一个典型的策略实现逻辑。进一步用策略类来模拟一下这个过程
先来写几个服务业务的类,
统一的返回消息对象
public class MessageResult implements Serializable {
private int code;
private Object data;
private String msg;
public MessageResult(String msg, int code, Object data) {
this.code = code;
this.data = data;
this.msg = msg;
}
@Override
public String toString() {
return "支付状态{" +
"code=" + code +
", data=" + data +
", msg='" + msg + '\'' +
'}';
}
}
订单类
/**
* 支付策略场景-订单类
*/
public class Order {
private String uid;
private String goodsName;
private String orderId;
private double amount;
public Order(String uid, String goodsName, String orderId, double amount) {
this.uid = uid;
this.goodsName = goodsName;
this.orderId = orderId;
this.amount = amount;
}
public MessageResult pay(String peyTypeCode) {
//根据支付code 获取到具体的支付策略
IPayment iPayment = PayStrategy.get(peyTypeCode);
System.out.printf("欢迎使用" + iPayment.getName() + "支付");
System.out.println("本次支付金额 :" + this.amount + "元");
return iPayment.pay(this.uid, this.amount);
}
}
其中IPayment
是所有支付策略实现的规范抽象,包含了支付方式名称,查询用户对应的余额,以及发起支付的抽象。
/**
* 支付抽象
*/
public abstract class IPayment {
//获取支付名称
public abstract String getName();
//查询余额
protected abstract double queryBalance(String uid);
//调用支付
public MessageResult pay(String uid, double amount) {
if (queryBalance(uid) < amount) {
return new MessageResult("支付失败", 502, "余额不足");
}
return new MessageResult("支付成功", 200, "支付金额:".concat(String.valueOf(amount)));
}
}
下面我们模式三个支付方的策略
/**
* 微信支付
*/
public class WechatPay extends IPayment {
@Override
public String getName() {
return "微信支付";
}
@Override
protected double queryBalance(String uid) {
return 3;
}
}
/**
* 支付宝支付的实现
*/
public class Alipay extends IPayment {
@Override
public String getName() {
return "支付宝";
}
@Override
protected double queryBalance(String uid) {
return 500;
}
}
/**
* 京东支付的实现
*/
public class JDPay extends IPayment {
@Override
public String getName() {
return "京东白条";
}
@Override
protected double queryBalance(String uid) {
return 350;
}
}
结合上面的优惠打折案例,这里也类似,构建集中支付方式的枚举|常量,在构建一个工厂方法,结合策略,实现感觉支付枚举,得到具体的支付策略的方法。
/**
* 支付策略
*/
public class PayStrategy {
//系统支付的支付方式常量
public static final String ALI_PAY = "Alipay";
public static final String JD_PAY = "JDPay";
public static final String WECHAT_PAY = "WechatPay";
public static final String DEFAULT_PAY = ALI_PAY;
//注册式单例 保存所有策略的对象
private static Map<String, IPayment> payStrategy = new HashMap<>();
static {
//根据支付方式常量,分配对应的支付实现策略
payStrategy.put(ALI_PAY, new Alipay());
payStrategy.put(JD_PAY, new JDPay());
payStrategy.put(WECHAT_PAY, new WechatPay());
}
/**
* 根据统一入口,实现动态策略
* @param payTypeCode
* @return
*/
public static IPayment get(String payTypeCode) {
if (payStrategy.containsKey(payTypeCode)) {
return payStrategy.get(payTypeCode);
}
return payStrategy.get(DEFAULT_PAY); //用户没传参,或传入有误时,使用默认的DEFAULT_PAY策略进行支付
}
测试-多种支付之间的选择
public static void main(String[] args) {
//实例化一个订单
Order order = new Order("1","糖果", "20210201001", 350);
String payType = PayStrategy.ALI_PAY;
//
MessageResult alipay = order.pay(payType);
System.out.println(alipay);
}
可以清楚的看到,当后续再需要添加一个支付方式时,我们只需要添加具体支付的业务策略,然后再将该策略添加到工厂类中即可,原本业务基本上可以不用改动。是不是很棒?!~!@
strategy
设计模式,经验的总结,知识为了解决实际问题,简化我们的工作量, 以及工作效率。
框架和JDk中的策略模式例子
Comparator 比较器
Comparable
InstantiationStrategy类初始化策略
策略模式的优缺点
优点
符合开闭原则
避免使用多重条件转移语句 ,如 if...else...语句、switch语句
使用策略模式可以提高算法的保密性和安全性
缺点
客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
代码中会产生非常多策略类,增加维护难度。
JDK源码
TreeMap
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
Spring中的源码
InstantiationStrategy //类初始化策略