设计模式-策略模式

2,801 阅读4分钟

策略模式又叫做政策模式,他是将定义的算法家族、分别封装起来,让他们之间可以相互替换,从而让算法的变化不会影响到使用算法的用户,属于行为型模式。

策略模式使用的就是面向对象的继承和多态机制,从未实现同一行为在不同场景下具备不同实现。

通俗点将就是在我们业务逻辑中很定有很多switch case或者是很多if else,我们的业务是需要通过场景判断选择其中一个逻辑执行,这个时候就可以使用策略模式(需要注意的是策略模式一定是只需要执行很多逻辑算法中的一个,而不是执行多个逻辑算法,如果是执行多个就不太适合使用策略模式)

策略模式的应用场景

举一个很常见的例子:支付。
我们接入不同的支付肯定是需要使用不同的sdk, 支付宝使用的是支付宝的sdk,微信使用的是微信的sdk。我们在支付的时候肯定只能选择一种方式支付,这个时候就比较适合使用策略模式.

总结一下:策略模式适用场景:

  1. 针对一个问题有多个不同的处理方式,每种方式之间没有耦合(可以独立解决问题)
  2. 算法逻辑需要自由切换的情况下
  3. 需要屏蔽算法逻辑规则的场景(应为高层是不接触算法逻辑的实现的)

我们可以看一下策略模式通用的UML类图: image.png 其中:

  • 上下文角色(Context):主要使用来操作策略上下文,做到屏蔽高层模块对策略、算法直接访问,也可以在这里封装可能存在的变化
  • 抽象策略角色(Strategy):主要是规定策略或者算法的行为
  • 具体策略角色(ConcreteStrategy):策略或者是算法的实现

支付案例实现(无设计模式情况下)

package com.example.demo.test.pay;

import java.util.Objects;

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 void pay(String payType){
        int currentAmount = 0;
        if(Objects.equals(payType, "AliPay")){
            //这里需要查询支付宝账户余额然后
            currentAmount = 900;
            if(this.amount > currentAmount){
                System.out.println("支付宝余额不足");
                return;
            }
            //调用支付宝SDK支付
            System.out.println("支付宝-支付成功");
        }else if(Objects.equals(payType, "WeChatPay")){
            //这里需要查询支付宝账户余额然后
            currentAmount = 600;
            if(this.amount > currentAmount){
                System.out.println("微信余额不足");
                return;
            }
            //调用支付宝SDK支付
            System.out.println("微信-支付成功");
        }else if(Objects.equals(payType, "UnionPay")){
            currentAmount = 100;
            if(this.amount > currentAmount){
                System.out.println("银行卡余额不足");
                return;
            }
            System.out.println("银行卡-支付成功");
        }

    }

}
public class Test {

    public static void main(String[] args) {
        Order order = new Order("123123", "333222", 977);
        order.pay("AliPay");
    }
}

这样设计的优点感觉一眼就能看出来,很多的支付逻辑都耦合在订单的模块,这样以后的维护和修改都很难,代码只会越来越低最后堆成屎山。

我们也可以发现这个支付就很适合使用策略模式,应为不可能有某个订单使用联合支付,所以我们在使用纯策略模式更改一下

支付案例实现(策略设计模式)

package com.example.demo.test.pay;

import java.util.Objects;

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 void pay(String payType){
        if(Objects.equals(payType, "AliPay")){
            AliPay aliPay = new AliPay();
            PayContext payContext = new PayContext(aliPay);
            payContext.pay(this.uid, this.amount);
        }else if(Objects.equals(payType, "WeChatPay")){
            WeChatPay weChatPay = new WeChatPay();
            PayContext payContext = new PayContext(weChatPay);
            payContext.pay(this.uid, this.amount);
        }else if(Objects.equals(payType, "UnionPay")){
            UnionPay unionPay = new UnionPay();
            PayContext payContext = new PayContext(unionPay);
            payContext.pay(this.uid, this.amount);
        }

    }

}
public interface IPay {
    String getName();
    Double queryBalance(String uid);
    default void pay(String uid,Double price){
        Double currentAmount  = queryBalance(uid);
        if(currentAmount < price){
            System.out.println(getName() + "余额不足");
        }else{
            System.out.println(getName() + "支付成功");
        }

    }
}
package com.example.demo.test.pay;
public class AliPay implements IPay{
    @Override
    public String getName() {
        return "支付宝";
    }

    @Override
    public Double queryBalance(String uid) {
        return 900.0;
    }
}
package com.example.demo.test.pay;

public class WeChatPay implements IPay{
    @Override
    public String getName() {
        return "微信支付";
    }

    @Override
    public Double queryBalance(String uid) {
        return 200.0;
    }
}
package com.example.demo.test.pay;

public class UnionPay implements IPay{
    @Override
    public String getName() {
        return "银行卡";
    }

    @Override
    public Double queryBalance(String uid) {
        return 10000.0;
    }
}

我们可以先看他一下UML类图: image.png 发现和上文说到策略模式的UML类图是一样的。但是我们在看具体的代码实现虽然我们说是将支付的算法逻辑实现抽离出来了,代码是比第一种实现方式要简洁一些了,但是还是省不掉if...else,所以说设计模式是一个设计代码的理念,具体到实现还是需要切合实际考虑比如要考虑具体的业务实现逻辑(当然支付这个业务逻辑还是很适合使用策略模式的),还有就是各个设计模式之间的融合。接下来我们在使用策略模式+单例模式+工厂模式再改造一下

支付案例实现(策略设计模式+单例+工厂模式)

package com.example.demo.test.pay;

import java.util.Objects;

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 void pay(String payType){
        IPay iPay = PayFactory.getMethod(payType);
        PayContext payContext = new PayContext(iPay);
        payContext.pay(this.uid, this.amount);
    }

}
package com.example.demo.test.pay;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class PayFactory {
    private static final Map<String,IPay> METHOD = new HashMap<>();

    static {
        METHOD.put(MethodKey.ALIPAY,new AliPay());
        METHOD.put(MethodKey.WECHAT_PAY,new WeChatPay());
        METHOD.put(MethodKey.UNION_PAY,new UnionPay());
    }

    private interface MethodKey {
        String ALIPAY = "ALIPAY";
        String WECHAT_PAY = "WECHAT_PAY";
        String UNION_PAY = "UNION_PAY";
    }

    private static final IPay defaultPay = new AliPay();

    public static IPay getMethod(String key){
        if(METHOD.containsKey(key)){
            return METHOD.get(key);
        }
        return defaultPay;
    }

    public static Set<String> getMethodKey(){
        return METHOD.keySet();
    }
}

其他的都不变我们只需要添加一个工厂类,我们可以定义好Map,通过type找到对应的map就可以完美实现了,我们再看一下order的代码这个时候就会发现很赶紧,很优雅,我们先修改某一个支付方式只需要到具体类修改就行,不需要在反复修改order中的pay方法,也符合了开闭原则。

当然这里也可以直接不要Context个人觉得也可以具体看自己的业务吧,活学活用