本文属于系列文章《设计模式》,附上文集链接
策略模式
- 定义:定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。
- 作用:首先是封装的算法,然后可相互替换,可以想象出一个场景,就是有很多种的选择,然后可以选择最合适的一种,如果不用策略模式的话,那就是一个一个自行选择,对的。
- 属于行为类模式
举个例子
之前做外包做一个网站,其中有一个模块是支付的,可供选择的方式有支付宝支付,微信支付和支付宝的跳转支付(H5跳转app支付)。当时是蠢的啊,没想到策略模式这种东西,上代码
// 支付控制器
public class PayController {
// 微信要自己生成二维码,若是在手机端,则必须要微信浏览器才能使用
public void wechatPay() {
// 模拟request收集到订单号,商品描述,总价钱的参数
String orderNo = System.currentTimeMillis() + "";
String body = "终极商店—大红苹果";
long totalFee = 6L;
// 模拟调用支付api的过程
System.out.println(
"使用订单号:" + orderNo + ",商品描述:" + body + "和总价钱:" + totalFee + "调用微信支付工具类,请求下单API,返回支付URL并根据URL生成二维码");
}
// 支付宝直接跳转到支付宝收集订单信息的jsp页面来发起网关支付,只能用于PC端
public void aliPay() {
// 模拟request收集到订单号,商品描述,总价钱的参数
String orderNo = System.currentTimeMillis() + "";
String body = "终极商店—大红苹果";
long totalFee = 6L;
// 模拟调用支付api的过程
System.out.println("将订单号:" + orderNo + ",商品描述:" + body + "和总价钱:" + totalFee + "作为参数,访问支付宝收集订单信息的jsp页面来发起网关支付");
}
// 移动端使用支付宝支付,跳转到支付宝收银台,支付宝收银台有唤醒支付宝APP的函数,但不能在微信浏览器打开
public void mobileAliPay() {
// 模拟request收集到订单号,商品描述,总价钱的参数
String orderNo = System.currentTimeMillis() + "";
String body = "终极商店—大红苹果";
long totalFee = 6L;
// 模拟调用支付api的过程
System.out.println("将订单号:" + orderNo + ",商品描述:" + body + "和总价钱:" + totalFee + "作为参数,访问支付宝的收银台来发起移动支付");
}
}
// 场景类
public class Client {
public static void main(String[] args) throws InterruptedException {
PayController payController = new PayController();
System.out.println("用户Tom选择了商品,然后开始下单支付");
System.out.println("用户选择了支付宝支付");
System.out.println("在PC端,使用Chrome浏览器操作");
payController.aliPay();
Thread.sleep(10);
System.out.println("-----------------------------------------");
System.out.println("用户Sivan选择了商品,然后开始下单支付");
System.out.println("用户选择了支付宝支付");
System.out.println("在移动端端,使用非微信浏览器操作");
payController.mobileAliPay();
Thread.sleep(10);
System.out.println("-----------------------------------------");
System.out.println("用户Jack选择了商品,然后开始下单支付");
System.out.println("用户选择了微信支付");
System.out.println("在移动端端,使用微信浏览器操作");
payController.wechatPay();
}
}
结果:
用户Tom选择了商品,然后开始下单支付
用户选择了支付宝支付
在PC端,使用Chrome浏览器操作
将订单号:1491293476471,商品描述:终极商店—大红苹果和总价钱:6作为参数,访问支付宝收集订单信息的jsp页面来发起网关支付
\-----------------------------------------
用户Sivan选择了商品,然后开始下单支付
用户选择了支付宝支付
在移动端端,使用非微信浏览器操作
将订单号:1491293476482,商品描述:终极商店—大红苹果和总价钱:6作为参数,访问支付宝的收银台来发起移动支付
\-----------------------------------------
用户Jack选择了商品,然后开始下单支付
用户选择了微信支付
在移动端端,使用微信浏览器操作
使用订单号:1491293476492,商品描述:终极商店—大红苹果和总价钱:6调用微信支付工具类,请求下单API,返回支付URL并根据URL生成二维码
代码就不解释了,注释都说明了,当时就是傻了,没想到扩展性等的问题。试想下,以后如果多一种支付方式,我就要在PayController多加一个方法,修改已有代码,不符合开闭原则喔。其次,上面的每个pay的方法都有相同的代码(requset获取参数)。。一点复用都没有,贼气(这里倒和策略模式无关,只是对自身实力的一种吐槽)。
用策略模式改造下,如下:
// 支付策略接口
public interface PayStrategy {
public void pay(String orderNo,String body,long totalFee);
}
//支付宝支付策略,直接跳转到支付宝收集订单信息的jsp页面来发起网关支付,只能用于PC端
public class AliPayStraegy implements PayStrategy {
@Override
public void pay(String orderNo, String body, long totalFee) {
// 模拟调用支付api的过程
System.out.println("将"
\+ "订单号:" + orderNo + ",商品描述:" + body + "和总价钱:" + totalFee + ""
\+ "作为参数,访问支付宝收集订单信息的jsp页面来发起网关支付");
}
}
// 移动端使用支付宝支付策略,跳转到支付宝收银台,支付宝收银台有唤醒支付宝APP的函数,但不能在微信浏览器打开
public class MobileAlipayStrategy implements PayStrategy{
@Override
public void pay(String orderNo,String body,long totalFee){
// 模拟调用支付api的过程
System.out.println("将"
\+ "订单号:" + orderNo + ",商品描述:" + body + "和总价钱:" + totalFee + ""
\+ "作为参数,访问支付宝的收银台来发起移动支付");
}
}
//微信支付策略,要自己生成二维码,若是在手机端,则必须要微信浏览器才能使用
public class WechatPayStrategy implements PayStrategy {
@Override
public void pay(String orderNo, String body, long totalFee) {
// 模拟调用支付api的过程
System.out.println("使用"
\+ "订单号:" + orderNo + ",商品描述:" + body + "和总价钱:" + totalFee + ""
\+ "调用微信支付工具类,请求下单API,返回支付URL并根据URL生成二维码");
}
}
// 支付控制器
public class PayController {
private PayStrategy payStrategy;
public void setPayStrategy(PayStrategy payStrategy) {
this.payStrategy = payStrategy;
}
public void pay() {
// 模拟request收集到订单号,商品描述,总价钱的参数
String orderNo = System.currentTimeMillis() + "";
String body = "终极商店—大红苹果";
long totalFee = 6L;
// 模拟调用支付api的过程
payStrategy.pay(orderNo, body, totalFee);
}
}
// 场景类
public class Client {
public static void main(String[] args) throws InterruptedException {
PayController payController = new PayController();
System.out.println("用户Tom选择了商品,然后开始下单支付");
System.out.println("用户选择了支付宝支付");
System.out.println("在PC端,使用Chrome浏览器操作");
payController.setPayStrategy(new AliPayStraegy());
payController.pay();
Thread.sleep(10);
System.out.println("-----------------------------------------");
System.out.println("用户Sivan选择了商品,然后开始下单支付");
System.out.println("用户选择了支付宝支付");
System.out.println("在移动端端,使用非微信浏览器操作");
payController.setPayStrategy(new MobileAlipayStrategy());
payController.pay();
Thread.sleep(10);
System.out.println("-----------------------------------------");
System.out.println("用户Jack选择了商品,然后开始下单支付");
System.out.println("用户选择了微信支付");
System.out.println("在移动端端,使用微信浏览器操作");
payController.setPayStrategy(new WechatPayStrategy());
payController.pay();
}
}
我们先定义了一个PayStrategy的接口,然后每一个支付的具体方法都实现这个接口,然后在PayController中组合了一个PayStrategy,定义了一个set方法,这个set方法,我把它称作“策略选择器”,高大上,哈哈。
然后在pay方法中,调用策略来执行pay方法就行了。然后在客户端,每次调用支付的接口的时候,就使用我们的“策略选择器”,使用指定的策略,然后就OK了。PayController在执行pay方法的时候会根据传入的PayStrategy们来选择相应的方法来执行,这就是简单的策略模式。
有啥好处?第一,以后每多一种支付方式,我只需要实现PayStrategy接口来新建一个类,而不需要修改原有代码,符合开闭原则。第二,假设原有的支付方式发生改变,需要修改,我只需要修改对应的策略类,避免了对其他的策略类造成影响的可能。第三,客户端对Controller的了解变少了,因为只需要了解策略的种类,而不需要了解Controller哪个方法具体是干啥的,也符合迪米特原则。
延伸下,当时我在外包中是用@Resource将PayStrategy的实现类都放到IOC容器,然后在PayController的payService(对的,并不是像代码那样在Controller直接组装的,啊哈哈),用@AutoWired组装每一个支付策略的,页面会传一个payType参数(对的,上面的代码还是没提到,啊哈哈),根据payType参数来使用相应的策略,来完成下单那个动作。如果有更好的办法的朋友欢迎拍砖,在此先谢谢了。
延伸
这个真的是延伸了,首先是看书看到的实现方法,觉得挺有意思的一种实现,叫策略枚举,也是666.
代码:
// 策略枚举
public enum Pay {
//支付宝支付策略,直接跳转到支付宝收集订单信息的jsp页面来发起网关支付,只能用于PC端
AliPay() {
@Override
public void pay(String orderNo, String body, long totalFee) {
// 模拟调用支付api的过程
System.out.println("将"
\+ "订单号:" + orderNo + ",商品描述:" + body + "和总价钱:" + totalFee + ""
\+ "作为参数,访问支付宝收集订单信息的jsp页面来发起网关支付");
}
},
//微信支付策略,要自己生成二维码,若是在手机端,则必须要微信浏览器才能使用
WechatPay() {
@Override
public void pay(String orderNo, String body, long totalFee) {
// 模拟调用支付api的过程
System.out.println("使用"
\+ "订单号:" + orderNo + ",商品描述:" + body + "和总价钱:" + totalFee + ""
\+ "调用微信支付工具类,请求下单API,返回支付URL并根据URL生成二维码");
}
},
// 移动端使用支付宝支付策略,跳转到支付宝收银台,支付宝收银台有唤醒支付宝APP的函数,但不能在微信浏览器打开
MobileAliPay() {
@Override
public void pay(String orderNo,String body,long totalFee){
// 模拟调用支付api的过程
System.out.println("将"
\+ "订单号:" + orderNo + ",商品描述:" + body + "和总价钱:" + totalFee + ""
\+ "作为参数,访问支付宝的收银台来发起移动支付");
}
};
// 定义支付的抽象方法,枚举类型每多一个值,都得实现这个抽象方法,就是这个特性才能666
public abstract void pay(String orderNo, String body, long totalFee);
}
// 场景类
public class Client {
public static void main(String[] args) throws InterruptedException {
// 偷偷懒,直接模拟那些参数了,勿喷
String orderNo = System.currentTimeMillis() + "";
String body = "终极商店—大红苹果";
long totalFee = 6L;
PayController payController = new PayController();
System.out.println("用户Tom选择了商品,然后开始下单支付");
System.out.println("用户选择了支付宝支付");
System.out.println("在PC端,使用Chrome浏览器操作");
// 直接使用策略的枚举值
Pay.AliPay.pay(orderNo, body, totalFee);
Thread.sleep(10);
System.out.println("-----------------------------------------");
System.out.println("用户Sivan选择了商品,然后开始下单支付");
System.out.println("用户选择了支付宝支付");
System.out.println("在移动端端,使用非微信浏览器操作");
// 直接使用策略的枚举值
Pay.MobileAliPay.pay(orderNo, body, totalFee);
Thread.sleep(10);
System.out.println("-----------------------------------------");
System.out.println("用户Jack选择了商品,然后开始下单支付");
System.out.println("用户选择了微信支付");
System.out.println("在移动端端,使用微信浏览器操作");
// 直接使用策略的枚举值
Pay.WechatPay.pay(orderNo, body, totalFee);
}
}
结果:
没错,上面就是策略枚举,当时第一次看到的时候惊了个大呆,枚举还可以这样玩。我在上面的示例偷了偷懒,正确的做法应该是在payController那里选择策略的,具体的代码脑补下吧。。。
虽然好像和很多看到的策略模式有很大的出入,但不得不说,这个并没有什么毛病,同一样东西的不同表达。看回定义,把每一个算法封装起来, 并且使它们可相互替换,而他们的算法实现都是在枚举值中实现的,相互替换也没什么毛病。毕竟扩展接口的方法也需要记住哪个类对应哪个算法,而枚举需要的记住某个枚举值对应某个算法,只不过就是如果需要构造函数做点什么事的话,枚举的方法就很蛋疼了。。懂的自然懂,哈哈。
上面那个是看书看到,下面这个就是我看Thinking In Java中看到内部类的时候突发奇想联想到的了,可能看到内部类应该已经想到大概怎么实现了吧,哈哈,来看代码:
// 使用内部类的策略模式
public class PayStrategyWithInnerClass {
//支付宝支付策略,直接跳转到支付宝收集订单信息的jsp页面来发起网关支付,只能用于PC端
public class AliPay implements PayStrategy{
@Override
public void pay(String orderNo, String body, long totalFee) {
// 模拟调用支付api的过程
System.out.println("将"
\+ "订单号:" + orderNo + ",商品描述:" + body + "和总价钱:" + totalFee + ""
\+ "作为参数,访问支付宝收集订单信息的jsp页面来发起网关支付");
}
}
//微信支付策略,要自己生成二维码,若是在手机端,则必须要微信浏览器才能使用
public class WechatPay implements PayStrategy{
@Override
public void pay(String orderNo, String body, long totalFee) {
// 模拟调用支付api的过程
System.out.println("使用"
\+ "订单号:" + orderNo + ",商品描述:" + body + "和总价钱:" + totalFee + ""
\+ "调用微信支付工具类,请求下单API,返回支付URL并根据URL生成二维码");
}
}
// 移动端使用支付宝支付策略,跳转到支付宝收银台,支付宝收银台有唤醒支付宝APP的函数,但不能在微信浏览器打开
public class MobileAliPay implements PayStrategy{
@Override
public void pay(String orderNo, String body, long totalFee) {
// 模拟调用支付api的过程
System.out.println("将"
\+ "订单号:" + orderNo + ",商品描述:" + body + "和总价钱:" + totalFee + ""
\+ "作为参数,访问支付宝的收银台来发起移动支付");
}
}
}
// 场景类
public class Client {
public static void main(String[] args) throws InterruptedException {
PayStrategyWithInnerClass payStrategy = new PayStrategyWithInnerClass();
PayController payController = new PayController();
System.out.println("用户Tom选择了商品,然后开始下单支付");
System.out.println("用户选择了支付宝支付");
System.out.println("在PC端,使用Chrome浏览器操作");
// 实现方式有所差别
payController.setPayStrategy(payStrategy.new AliPay());
payController.pay();
// 直接使用策略的枚举值
Thread.sleep(10);
System.out.println("-----------------------------------------");
System.out.println("用户Sivan选择了商品,然后开始下单支付");
System.out.println("用户选择了支付宝支付");
System.out.println("在移动端端,使用非微信浏览器操作");
payController.setPayStrategy(payStrategy.new MobileAliPay());
payController.pay();
Thread.sleep(10);
System.out.println("-----------------------------------------");
System.out.println("用户Jack选择了商品,然后开始下单支付");
System.out.println("用户选择了微信支付");
System.out.println("在移动端端,使用微信浏览器操作");
payController.setPayStrategy(payStrategy.new WechatPay());
payController.pay();
}
}
结果:
用户Tom选择了商品,然后开始下单支付
用户选择了支付宝支付
在PC端,使用Chrome浏览器操作
将订单号:1491315431218,商品描述:终极商店—大红苹果和总价钱:6作为参数,访问支付宝收集订单信息的jsp页面来发起网关支付
\-----------------------------------------
用户Sivan选择了商品,然后开始下单支付
用户选择了支付宝支付
在移动端端,使用非微信浏览器操作
将订单号:1491315431230,商品描述:终极商店—大红苹果和总价钱:6作为参数,访问支付宝的收银台来发起移动支付
\-----------------------------------------
用户Jack选择了商品,然后开始下单支付
用户选择了微信支付
在移动端端,使用微信浏览器操作
使用订单号:1491315431241,商品描述:终极商店—大红苹果和总价钱:6调用微信支付工具类,请求下单API,返回支付URL并根据URL生成二维码
其实也没啥不同,内部类来实现策略PayStrategy接口,然后实现方法,在调用的场景也是实例化一个内部类而已,实现的实质也是策略选择器。。内部类那里的外围类有点像枚举,但是又有点差别。没遇到实际问题也想不出来啥例子,有经验的人士麻烦评论区拍个砖,感激不尽。
以上就是策略模式,水平有限,难免有错,欢迎评论区指责