谈笑间学会策略模式

1 背景

某天早上,Skr郎正在一边悠哉的吃着加了三个鸡蛋的手抓饼,一边悠闲地逛着论坛,看着沙雕网友的帖子,Skr郎会心一笑,正欲给沙雕帖子点赞,邮件忽的弹出,Skr郎慢悠悠的打开邮件

任务: 对接阿里支付接口
时限: 一周

要求如下:
	1. 单笔限额不超过1W
	2. 可以成功提现
	3. 发起提现后,可以主动查询提现结果

Skr郎心中暗喜,看我两天搞定,剩下的时间就可以嘿嘿嘿了

/**
 * 阿里业务处理
 *
 * @author gp6
 * @date 2020/3/13
 */
public class AlibabaHandler {

    void singleQuota() {
        System.out.println("阿里校验单笔限额");
    }

    void pay() {
        System.out.println("阿里支付逻辑");
    }

    void getResult() {
        System.out.println("阿里主动查询支付结果");
    }
}


/**
 * 支付服务类
 *
 * @author gp6
 * @date 2020/3/13
 */
public class PayService {

    public static void main(String[] args) {
       // 支付接口(第一个方法)
        AlibabaHandler alibabaHandler = new AlibabaHandler();
        alibabaHandler.singleQuota();
        alibabaHandler.pay();

        // 查询接口(第二个方法)
        AlibabaHandler alibabaHandler1 = new AlibabaHandler();
        alibabaHandler1.getResult();
    }
}

Skr郎看着完成的代码,心中评价道:这段代码,逻辑严谨,注释清晰,优雅中透露着洒脱,洒脱中透露着不羁,完美!

正洋洋自得间,项目经理喊道:Skr郎,你过来一下!Skr郎赶紧屁颠屁颠的跑过去.

项目经理:临时加个需求,把微信支付也接进来,根据前台传的渠道,使用不同的支付方式,时间不加.

Skr郎:好的,没问题(MMP)

/**
 * 微信业务处理
 *
 * @author gp6
 * @date 2020/3/13
 */
public class WechatHandler {

    void singleQuota() {
        System.out.println("微信校验单笔限额");
    }

    void pay() {
        System.out.println("微信支付逻辑");
    }

    void getResult() {
        System.out.println("微信主动查询支付结果");
    }
}


/**
 * 支付服务类
 *
 * @author gp6
 * @date 2020/3/13
 */
public class PayService {

    public static void main(String[] args) {
        // 前台传的渠道 1:阿里 2:微信
        String channel = "1";

        // 支付逻辑(第一个方法)
        if ("1".equals(channel)) {
            // 阿里支付接口
            AlibabaHandler alibabaHandler = new AlibabaHandler();
            alibabaHandler.singleQuota();
            alibabaHandler.pay();
        } else if ("2".equals(channel)) {
            // 微信支付接口
            WechatHandler wechatHandler = new WechatHandler();
            wechatHandler.singleQuota();
            wechatHandler.pay();
        }


        // 查询接口(第二个方法)
        if ("1".equals(channel)) {
            // 阿里查询
            AlibabaHandler alibabaHandler = new AlibabaHandler();
            alibabaHandler.getResult();
        } else if ("2".equals(channel)) {
            // 微信查询
            WechatHandler wechatHandler = new WechatHandler();
            wechatHandler.getResult();
        }
    }
}


Skr郎看着完成的代码,心中评价道:这段代码,逻辑严谨,注释清晰,优雅中透露着洒脱,洒脱中透露着不羁,虽然没有上一版优雅,但还是比较不错的!

正洋洋自得间,又听项目经理喊道:Skr郎,你过来一下!Skr郎心里想着如果这次加需求不加时间,老子坚决不做!

项目经理:临时在加个需求,把通联和Ping++支付也接进来,根据前台传的渠道,使用不同的支付方式,时间不加.

Skr郎:好的,没问题(心里问候一下他的族中长辈)


/**
 * Ping++业务处理
 *
 * @author gp6
 * @date 2020/3/13
 */
public class PingHandler {

    void singleQuota() {
        System.out.println("Ping++校验单笔限额");
    }

    void pay() {
        System.out.println("Ping++支付逻辑");
    }

    void getResult() {
        System.out.println("Ping++主动查询支付结果");
    }
}


/**
 * 通联业务处理
 *
 * @author gp6
 * @date 2020/3/13
 */
public class AllinpayHandler {

    void singleQuota() {
        System.out.println("通联校验单笔限额");
    }

    void pay() {
        System.out.println("通联支付逻辑");
    }

    void getResult() {
        System.out.println("通联主动查询支付结果");
    }
}


/**
 * 支付服务类
 *
 * @author gp6
 * @date 2020/3/13
 */
public class PayService {

    public static void main(String[] args) {
        // 前台传的渠道 1:阿里 2:微信 3:Ping++ 4:通联
        String channel = "1";

        // 支付逻辑(第一个方法)
        if ("1".equals(channel)) {
            // 阿里支付接口
            AlibabaHandler alibabaHandler = new AlibabaHandler();
            alibabaHandler.singleQuota();
            alibabaHandler.pay();
        } else if ("2".equals(channel)) {
            // 微信支付接口
            WechatHandler wechatHandler = new WechatHandler();
            wechatHandler.singleQuota();
            wechatHandler.pay();
        }else if ("3".equals(channel)) {
            // Ping++支付接口
            PingHandler pingHandler = new PingHandler();
            pingHandler.singleQuota();
            pingHandler.pay();
        }else if ("4".equals(channel)) {
            // 通联支付接口
            AllinpayHandler allinpayHandler = new AllinpayHandler();
            allinpayHandler.singleQuota();
            allinpayHandler.pay();
        }


        // 查询接口(第二个方法)
        if ("1".equals(channel)) {
            // 阿里查询
            AlibabaHandler alibabaHandler = new AlibabaHandler();
            alibabaHandler.getResult();
        } else if ("2".equals(channel)) {
            // 微信查询
            WechatHandler wechatHandler = new WechatHandler();
            wechatHandler.getResult();
        }else if ("3".equals(channel)) {
            // Ping++查询
            PingHandler pingHandler = new PingHandler();
            pingHandler.getResult();
        }else if ("4".equals(channel)) {
            // 通联查询
            AllinpayHandler allinpayHandler = new AllinpayHandler();
            allinpayHandler.getResult();
        }
    }
}

Skr郎看着完成的代码,心中评价道:这段代码,逻辑严谨,注释清晰…我呸,这写的什么鬼东西!看着长长的if else,Skr郎陷入沉思…

如果后面再加新的渠道,是不是每次都要改动PayService中的调用逻辑,而且如果有别的方法要用此段逻辑,if else 又要复制一遍

脑中过滤着以往所学的设计模式…突然,灵光一闪,策略模式

2 策略模式

定义:
	定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户

看了策略模式的定义,是不是很符合这种场景

定义了一系列算法(支付逻辑),并将每个算法(支付逻辑)封装起来,使它们可以相互替换(根据不同channel切换不同的支付逻辑),且算法的变化不会影响使用算法的客户(PayService)

简直完美,Skr郎沾沾自喜

/**
 * 支付的抽象策略类
 * 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现
 *
 * @author gp6
 * @date 2020/3/13
 */
public interface PayHandler {

    /**
     * 单笔限额
     */
    void singleQuota();

    /**
     * 支付
     */
    void pay();

    /**
     * 提现结果查询
     */
    void getResult();
}


/**
 * 微信业务处理
 * 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现
 *
 * @author gp6
 * @date 2020/3/13
 */
public class WechatHandler implements PayHandler {
    public void singleQuota() {
        System.out.println("微信校验单笔限额");
    }

    public void pay() {
        System.out.println("微信支付逻辑");
    }

    public void getResult() {
        System.out.println("微信主动查询支付结果");
    }
}


/**
 * 阿里业务处理
 * 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现
 *
 * @author gp6
 * @date 2020/3/13
 */
public class AlibabaHandler implements PayHandler {

    public void singleQuota() {
        System.out.println("阿里校验单笔限额");
    }

    public void pay() {
        System.out.println("阿里支付逻辑");
    }

    public void getResult() {
        System.out.println("阿里主动查询支付结果");
    }
}

通联与Ping++代码类似


/**
 * 环境(Context)类:持有一个策略类的引用,最终给客户端调用
 *
 * @author gp6
 * @date 2020/3/13
 */
public class Context {

    private PayHandler payHandler;

    public Context(PayHandler payHandler){
        this.payHandler = payHandler;
    }

    /**
     * 单笔限额
     */
    void singleQuota() {
        payHandler.singleQuota();
    }

    /**
     * 支付
     */
    void pay() {
        payHandler.pay();
    }

    /**
     * 提现结果查询
     */
    void getResult() {
        payHandler.getResult();
    }
}


/**
 * 支付服务类
 *
 * @author gp6
 * @date 2020/3/13
 */
public class PayService {

    public static void main(String[] args) {
        // 前台传的渠道 1:阿里 2:微信 3:Ping++ 4:通联
        Integer channel = 1;

        // 支付逻辑(第一个方法)
        Context context;
        switch (channel) {
            case 1:
                context = new Context(new AlibabaHandler());
                break;
            case 2:
                context = new Context(new WechatHandler());
                break;
            case 3:
                context = new Context(new PingHandler());
                break;
            case 4:
                context = new Context(new AllinpayHandler());
                break;
            default:
                throw new RuntimeException("渠道不支持!");
        }
        context.singleQuota();
        context.pay();

        // 查询接口(第二个方法)
        Context context2;
        switch (channel) {
            case 1:
                context2 = new Context(new AlibabaHandler());
                break;
            case 2:
                context2 = new Context(new WechatHandler());
                break;
            case 3:
                context2 = new Context(new PingHandler());
                break;
            case 4:
                context2 = new Context(new AllinpayHandler());
                break;
            default:
                throw new RuntimeException("渠道不支持!");
        }
        context2.getResult();
    }
}

使用了策略模式,但是调用者(PayService)中,还是有重复的大量的if else

根据不用的渠道创建不同的具体策略类(new AlibabaHandler()),这不是典型的工厂模式吗…

3 策略模式结合工厂模式

将工厂模式放入策略环境中

/**
 * 环境(Context)类:持有一个策略类的引用,最终给客户端调用
 *
 * @author gp6
 * @date 2020/3/13
 */
public class Context {

    private PayHandler payHandler;

    public Context(Integer channel){
        switch (channel) {
            case 1:
                payHandler = new AlibabaHandler();
                break;
            case 2:
                payHandler = new WechatHandler();
                break;
            case 3:
                payHandler = new PingHandler();
                break;
            case 4:
                payHandler = new AllinpayHandler();
                break;
            default:
                throw new RuntimeException("渠道不支持!");
        }
    }

    /**
     * 单笔限额
     */
    void singleQuota() {
        payHandler.singleQuota();
    }

    /**
     * 支付
     */
    void pay() {
        payHandler.pay();
    }

    /**
     * 提现结果查询
     */
    void getResult() {
        payHandler.getResult();
    }
}


/**
 * 支付服务类
 *
 * @author gp6
 * @date 2020/3/13
 */
public class PayService {

    public static void main(String[] args) {
        // 前台传的渠道 1:阿里 2:微信 3:Ping++ 4:通联
        Integer channel = 1;

        // 支付逻辑(第一个方法)
        Context context = new Context(channel);
        context.singleQuota();
        context.pay();

        // 查询接口(第二个方法)
        Context context2  = new Context(channel);
        context2.getResult();
    }
}

此时如果再添加渠道,只需在Context的构造方法的判断逻辑中添加新的渠道,然后在相应的支付逻辑类中写入相应逻辑,只在策略环境中存在if else,调用者无需写重复的if else


 switch (channel) {
    case 1:
        payHandler = new AlibabaHandler();
        break;
    case 2:
        payHandler = new WechatHandler();
        break;
    case 3:
        payHandler = new PingHandler();
        break;
    case 4:
        payHandler = new AllinpayHandler();
        break;

    // 新的渠道
    case 5:
    	payHandler = new XXXHandler();
    	break;
    default:
        throw new RuntimeException("渠道不支持!");
}

/**
 * 新渠道的业务逻辑处理
 * 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现
 *
 * @author gp6
 * @date 2020/3/13
 */
public class XXXHandler implements PayHandler{
	.....
}

写到此处Skr郎欣喜的点点头,暗自得意,正打算将代码提交,忽然眉头一皱,每次添加新的渠道,都要修改判断逻辑,不是违背了 开闭原则 吗
Skr再次沉思

4 使用反射遵循开闭原则

/**
 * 环境(Context)类:持有一个策略类的引用,最终给客户端调用
 *
 * @author gp6
 * @date 2020/3/13
 */
public class Context {

    private PayHandler payHandler;

    public Context(PayEnum payEnum){
        try {
            payHandler= (PayHandler) Class.forName(payEnum.getClassName()).newInstance();
        } catch (Exception e) {
            throw new RuntimeException("渠道错误!");
        }
    }


    /**
     * 单笔限额
     */
    void singleQuota() {
        payHandler.singleQuota();
    }

    /**
     * 支付
     */
    void pay() {
        payHandler.pay();
    }

    /**
     * 提现结果查询
     */
    void getResult() {
        payHandler.getResult();
    }
}


/**
* 策略枚举
*
* @author gp6
* @date 2020/3/13
*/
public enum PayEnum {

    /**
     * 渠道对应类全路径
     */
    OPERATION_ADD("com.gp6.pay.AlibabaHandler", 1),
    OPERATION_SUB("com.gp6.pay.WechatHandler", 2),
    OPERATION_MUL("com.gp6.pay.PingHandler", 3);

    PayEnum(String className, Integer channel) {
        this.className = className;
        this.channel = channel;
    }

    /**
     * 类的全路径
     */
    private String className;

    /**
     * 渠道
     */
    private Integer channel;

    /**
     * 匹配渠道
     *
     * @param channel 渠道
     * @return 相关枚举
     */
    public static PayEnum matchChannel(Integer channel) {
        for (PayEnum payEnum : PayEnum.values()) {
            if (payEnum.getChannel().equals(channel)) {
                return payEnum;
            }
        }
        throw new RuntimeException("渠道不支持!");
    }

    public String getClassName() {
        return className;
    }

    public Integer getChannel() {
        return channel;
    }
}



/**
 * 支付服务类
 *
 * @author gp6
 * @date 2020/3/13
 */
public class PayService {

    public static void main(String[] args) {
        // 前台传的渠道 1:阿里 2:微信 3:Ping++ 4:通联
        Integer channel = 1;

        // 支付逻辑(第一个方法)
        Context context = new Context(PayEnum.matchChannel(channel));
        context.singleQuota();
        context.pay();

        // 查询接口(第二个方法)
        Context context2 = new Context(PayEnum.matchChannel(channel));
        context2.getResult();
    }
}

此时,如果想添加通联的渠道只需在PayEnum中添加

OPERATION_MUL("com.gp6.pay.AllinpayHandler", 4)

然后在com.gp6.pay.AllinpayHandler中撰写相应逻辑即可

Skr郎写到此处,心满意足的点点头,心中评价道:这段代码,逻辑严谨,注释清晰,优雅中透露着洒脱,洒脱中透露着不羁,真是"此码只应天上有,人间难得几回寻!"
呀! 想不到我作诗的功夫也如此深厚,哎,如此完美的男人,别人只能羡慕嫉妒恨!

5 策略模式类图

谈笑间学会策略模式_第1张图片

StrategyContext相当于文章中的Context
StrategyOperation相当于文章中的PayHandle
StrategyOperationAdd与StrategyOperationMul相当于文章中的AlibabaHandler/AllinpayHandler/PingHandler/WechatHandler

你可能感兴趣的:(23种设计模式)