设计模式之-策略模式2021-02-02

策略模式

策略模式的定义

策略模式(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 //类初始化策略
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容

  • 策略模式(Strategy Pattern)也叫做政策模式(Policy Pattern),是一种行为型模式。 一...
    l只为终点阅读 496评论 1 3
  • 排序问题: 当要比较的是整数,浮点数,都需要不同的排序方法,并且当改变了要比较的东西时要重新排序,这样不好, 怎样...
    tracy_668阅读 289评论 0 2
  • 本文的主要内容: 介绍策略模式 示例商场购物打折策略的实现 策略模式总结 源码分析策略模式的典型应用Java Co...
    小旋锋的简书阅读 1,300评论 0 1
  • 设计原则 1.开闭原则2.依赖倒置原则3.单一职责原则4.接口隔离原则5.迪米特法则(最少知道原则)6.里式替换原...
    纵横Top阅读 534评论 0 0
  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,622评论 2 17