一、策略模式概述:
首先我们来看一下什么是策略模式:
策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。 -阎宏博士 《JAVA与模式》
其实策略模式就是将每一个算法(变化的逻辑)封装起来,让其独立于使用它的客户而变化,并且使它们还可以相互替换。
二、策略模式中的角色及其职责:
1、抽象策略角色[Strategy]策略类:
定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,Context使用这个接口调用不同的算法,一般使用接口或抽象类实现。
2、具体策略类[(ConcreteStrategy)]:
实现了Strategy定义的接口,包装了相关的算法和行为,提供具体的算法实现。
3、上下文角色[Context]:客户端的基类(可以是一个抽象类)
持有一个策略类的引用,最终给客户端调用,具有以下4点特征:
(1)需要提供获取具体的算法(或策略类)的方法;
(2)内部维护一个策略类的实例;
(3)负责动态设置运行时Strategy具体的实现算法;
(4)负责跟策略类及其实现类之间的交互和数据传递;
4、角色设置如下图所示:
三、实例引入:
1、需求描述:
我们现在要开发一个类似于美团和饿了吗的外卖点餐平台,名字暂定为一米阳光。面向的客户主要有3类人,公司白领、农民伯伯和公司老板,客户在一米阳光平台点餐后,可以选择三种支付方式:支付宝、微信和信用卡。
需求实现:公司白领-小张使用支付宝支付100元,农民伯伯-老王使用微信支付50元,公司老板-李总使用信用卡支付200元。
2、需求分析:
在我们这个需求中,策略就是点餐费用的支付,具体的实现策略目前有三种:支付宝支付、微信支付和信用卡支付;
借助于策略模式,我们对于一米阳光平台的代码做了如下的设计:
(1)1个策略类(接口):PayStrategy;
(2)3个具体策略类:AliPay、WeChatPay和CreditCardPay;
(3)1个上下文类(客户基类、抽象类):Customer;
(4)3个客户类:WhiteCollar、Farmer和BusinessMan;
对应关系如下图所示:
四、代码呈现:
1、1个策略类(接口):PayStrategy:
package com.strategy;
/**
* 支付策略类
* */
public interface PayStrategy {
/**
* 支付方法
* customerName 客户姓名
* money 客户支付金额
* */
String pay(String customerName, Integer money);
}
2、3个具体策略类:AliPay、WeChatPay和CreditCardPay:
(1)AliPay
package com.strategy;
/**
* 支付宝支付
* */
public class AliPay implements PayStrategy{
@Override
public String pay(String customerName, Integer money) {
StringBuffer result = new StringBuffer();
result.append(customerName).append("您好,您在一米阳光平台使用");
//todo 支付宝支付
result.append("支付宝支付了");
result.append(money).append("元");
return result.toString();
}
}
(2)WeChatPay
package com.strategy;
/**
* 微信支付
* */
public class WeChatPay implements PayStrategy{
@Override
public String pay(String customerName, Integer money) {
StringBuffer result = new StringBuffer();
result.append(customerName).append("您好,您在一米阳光平台使用");
//todo 微信支付
result.append("微信支付了");
result.append(money).append("元");
return result.toString();
}
}
(3)CreditCardPay
package com.strategy;
/**
* 信用卡支付
* */
public class CreditCardPay implements PayStrategy{
@Override
public String pay(String customerName, Integer money) {
StringBuffer result = new StringBuffer();
result.append(customerName).append("您好,您在一米阳光平台使用");
//todo 信用卡支付
result.append("信用卡支付了");
result.append(money).append("元");
return result.toString();
}
}
3、1个上下文类(客户基类、抽象类):Customer:
package com.customer;
import com.strategy.PayStrategy;
/**
* 客户
* */
public abstract class Customer{
private String customerName;
private Integer payMoney;
public void setCustomerName(String customerName){
this.customerName = customerName;
}
public void setPayMoney(Integer payMoney){
this.payMoney = payMoney;
}
public String getCustomerName(){
return this.customerName;
}
public Integer getPayMoney(){
return this.payMoney;
}
private PayStrategy payStrategy;
public String payInstance(PayStrategy payStrategy){
return payStrategy.pay(customerName, payMoney);
}
}
4、3个客户类:WhiteCollar、Farmer和BusinessMan:
(1)WhiteCollar
package com.customer;
import com.customer.Customer;
/**
* 公司白领
* */
public class WhiteCollar extends Customer {
}
(2)Farmer
package com.customer;
import com.customer.Customer;
/**
* 农民伯伯
* */
public class Farmer extends Customer {
}
(3)BusinessMan
package com.customer;
/**
* 公司老板
* */
public class BusinessMan extends Customer {
}
5、客户端测试类:Client:
package com.customer.test;
import com.customer.BusinessMan;
import com.customer.Farmer;
import com.customer.WhiteCollar;
import com.strategy.AliPay;
import com.strategy.CreditCardPay;
import com.strategy.PayStrategy;
import com.strategy.WeChatPay;
public class Client {
public static void main(String[] args) {
//公司白领-小张,支付宝支付,100元
//选择并创建需要使用的策略对象
PayStrategy aliPay = new AliPay();
//创建环境
WhiteCollar whiteCollar = new WhiteCollar();
//计算价格
whiteCollar.setCustomerName("公司白领-小张");
whiteCollar.setPayMoney(100);
//支付
String payResult1 = whiteCollar.payInstance(aliPay);
//打印结果
System.out.println(payResult1);
//农民伯伯-老王,微信支付,50元
//选择并创建需要使用的策略对象
PayStrategy weChatPay = new WeChatPay();
//创建环境
Farmer farmer = new Farmer();
//计算价格
farmer.setCustomerName("农民伯伯-老王");
farmer.setPayMoney(50);
//支付
String payResult2 = farmer.payInstance(weChatPay);
//打印结果
System.out.println(payResult2);
//公司老板-李总,信用卡支付,200元
//选择并创建需要使用的策略对象
PayStrategy creditCardPay = new CreditCardPay();
//创建环境
BusinessMan businessMan = new BusinessMan();
//计算价格
businessMan.setCustomerName("公司老板-李总");
businessMan.setPayMoney(200);
//支付
String payResult3 = businessMan.payInstance(creditCardPay);
//打印结果
System.out.println(payResult3);
}
}
6、测试结果:
五、策略模式的设计原则:
策略模式的设计原则可以归纳为:变化的抽象成接口,面向接口编程而不是面向实现编程;
具体如下:
1、分开变化和不变化的部分,把变化的抽象成接口:
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。这个概念很简单,几乎是每个设计模式背后的精神所在,所有的模式都提供了一套方法让系统中的某部分改变不会影响其它部分;
比如在我们的一米阳光点餐平台中,固定的部分是每个客户都有姓名和支付费用两个属性,变化的部分是每个客户会采用不同的支付方式,从而我们需要把客户的支付方式封装成一个支付接口(策略类),并针对每一种支付方式设计了具体的支付策略/算法来实现这个支付接口(策略类);
2、针对接口编程,而不是针对实现编程:
关于接口编程和实现编程:
在我们开发过程中,面向接口编程比面向实现编程的可扩展性更好,比如在一米阳光点餐系统中:
(1)如果我们采用面向实现编辑的方式,那么为了满足公司白领-小张使用支付宝支付的需求,需要给小张加一个支付方式的实现,同样农民伯伯-老王和公司老总-李总也需要单独加一个支付方式的实现,如果后面来了一个新客户,选择了与小张、老王或李总相同的支付方式,我们也需要给他添加一段支付的代码;后续随着我们的客户越来也多,重复的代码也会越来越多,后续的小伙伴维护起代码来就会很吃力;同时如果后续有新的需求,一个客户可以选择多种支付方式,那么我们客户侧的代码,还需要添加很多的if-else判断来支持,这样会造成客户侧支付相关的代码越来越多越来越乱,无法支持快速的扩展,后续的维护也是一个大问题;
(2)如果我们采取面向接口实现,那我们就可以把支付相关的代码独立出来维护,后续新增支付方式,我们只需要新增一个具体的支付策略类;我们也可以通过在客户侧的简单调用,实现客户可以选择多种支付方式的需求;把变化的支付策略独立出来后,后续支付相关需求的变化对代码的侵入性和影响就很少了,代码的可扩展性就提高了;
3、多用组合,少用继承
使用组合建立系统具有很大的弹性,不仅可将算法族封装成类,更可以在运行时动态地改变行为;
“有一个”(has a)可能比“是一个”(is a)更好
在我们的一米阳光点餐平台中,每一个客户支付时都有一个PayStrategy,后续如果再给客户一个选择配送时间的SendStrategy,那么客户支付和配送时间选择的行为都可以委托它们代为处理;
客户在选择支付方式和配送时间时,我们就可以将两个类(PayStrategy和SendStrategy)结合起来使用,这就是组合(Composition)。这种作法和继承不同的地方在于:客户的行为不是继承而来,而是和适当的行为对象组合而来;
简而言之,在某些场景下,组合是比继承更优的解决方案,选择继承时,我们会受限于父类的既定方法,无法实现某些特定的扩展,但是组合可以让我们使用更简单的组装实现需求,而无需考虑父类的情况;
六、策略模式的优缺点
1、优点:
(1)策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,从而避免重复的代码。
(2)策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
(3)使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
2、缺点:
(1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
(2)策略模式造成很多的策略类,每个具体策略类都会产生一个新类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。
七、策略模式的应用场景:
1、多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为;
2、需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现;
3、对客户隐藏具体策略(算法)的实现细节,彼此完全独立;
4、具体使用场景:
(1)支付平台需要给不同客户提供不同的支付方式或渠道,支付方式或渠道独立,每一种都是一个算法;
(2)针对于不同消费者设置不同的会员等级和不同的优惠政策,会员等级和优惠政策相对独立,每一种都是一个算法;
结语:以上内容是自己对于策略模式的基本理解,如果有问题,欢迎大家来指证和讨论!