一 介绍
策略模式(strategy pattern)的原始定义是:定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。策略模式让算法可以独立于使用它的客户端而变化。**
二 原理
策略模式的本质就是通过Context类作为控制单元,对不同的策略进行调度分配
三 结构图
四 应用场景
其实我们在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等。
在软件开发中,经常会遇到这种情况,开发一个功能可以通过多个算法去实现,我们可以将所有的算法集中在一个类中,在这个类中提供多个方法,每个方法对应一个算法, 或者我们也可以将这些算法都封装在一个统一的方法中,使用if...else...等条件判断语句进行选择.但是这两种方式都存在硬编码的问题,后期需要增加算法就需要修改源代码,这会导致代码的维护变得困难.
在软件开发中,经常会遇到这种情况,开发一个功能可以通过多个算法去实现,我们可以将所有的算法集中在一个类中,在这个类中提供多个方法,每个方法对应一个算法, 或者我们也可以将这些算法都封装在一个统一的方法中,使用if...else...等条件判断语句进行选择.但是这两种方式都存在硬编码的问题,后期需要增加算法就需要修改源代码,这会导致代码的维护变得困难.
**比如网购,你可以选择工商银行、农业银行、建设银行等等,但是它们提供的算法都是一致的,就是帮你付款。**
五 实例1
4.1 策略抽象类和策略具体类
/**
* 抽象策略类
**/
public interface Strategy {
void algorithm();
}
/**
* 具体策略类
**/
public class ConcreteStrategyA implements Strategy {
@Override
public void algorithm() {
System.out.println("执行策略A");
}
}
/**
**/
public class ConcreteStrategyB implements Strategy {
@Override
public void algorithm() {
System.out.println("执行策略B");
}
}
4.2 的策略进行调度分配
/**
* 上下文类: 策略模式的本质就是通过Context类作为控制单元,对不同的策略进行调度分配
**/
public class Context {
//维持一个抽象策略的引用
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
//调用策略类中的算法
public void algorithm(){
strategy.algorithm();
}
}
4.3 调用
/**
**/
public class Client {
public static void main(String[] args) {
Strategy strategyB= new ConcreteStrategyB();
Context context = new Context(strategyB);
context.algorithm();
}
}
六 实例2
面试问题: 如何用设计模式消除代码中的if-else
物流行业中,通常会涉及到EDI报文(XML格式文件)传输和回执接收,每发送一份EDI报文,后续都会收到与之关联的回执(标识该数据在第三方系统中的流转状态)。
这里列举几种回执类型:MT1101、MT2101、MT4101、MT8104,系统在收到不同的回执报文后,会执行对应的业务逻辑处理。我们就业回执处理为演示案例
5.1 策略抽象类和策略具体类
/**
* 回执处理策略接口
**/
public interface ReceiptHandleStrategy {
void handleReceipt(Receipt receipt);
}
/**
具体策略类1
**/
public class MT2101ReceiptHandleStrategy implements ReceiptHandleStrategy {
@Override
public void handleReceipt(Receipt receipt) {
System.out.println("解析报文 MT2101: " + receipt.getMessage());
}
}
/**
* 具体策略类2
**/
public class MT1101ReceiptHandleStrategy implements ReceiptHandleStrategy {
@Override
public void handleReceipt(Receipt receipt) {
System.out.println("解析报文MT1101: " + receipt.getMessage());
}
}
5.2 策略类
/**
* 上下文类, 持有策略接口,决定执行哪一个具体的策略类
**/
public class ReceiptStrategyContext {
private ReceiptHandleStrategy receiptHandleStrategy;
public void setReceiptHandleStrategy(ReceiptHandleStrategy receiptHandleStrategy) {
this.receiptHandleStrategy = receiptHandleStrategy;
}
//调用策略类中方法
public void handleReceipt(Receipt receipt){
if(receipt != null){
receiptHandleStrategy.handleReceipt(receipt);
}
}
}
5.3 策略工厂类消除clien里面使用具体策略时的if ... else
/**
* 策略工厂类
**/
public class ReceiptHandleStrategyFactory {
public ReceiptHandleStrategyFactory() {
}
//使用Map集合存储策略信息,彻底的消除if...else
private static Map<String,ReceiptHandleStrategy> strategyMap;
//初始化具体策略,保存到map集合
public static void init(){
strategyMap = new HashMap<>();
// strategyMap.put("MT1101",new MT1101ReceiptHandleStrategy());
// strategyMap.put("MT2101",new MT2101ReceiptHandleStrategy());
try {
SAXReader reader = new SAXReader();
String file = "I:\\MSB\\msb_work\\designpattern\\msb-strategy-pattern-14\\src\\main\\resources\\config.xml";
Document document = reader.read(file);
Node node = document.selectSingleNode("/confing/className");
String className = node.getText();
Class clazz = Class.forName(className);
ReceiptHandleStrategy strategy = (ReceiptHandleStrategy) clazz.newInstance();
strategyMap.put("MT1101",strategy);
} catch (Exception e) {
e.printStackTrace();
}
}
//根据回执类型,获取对应的策略对象
public static ReceiptHandleStrategy getStrategy(String receiptType){
return strategyMap.get(receiptType);
}
}
5.4 调用
/**
**/
public class Client {
public static void main(String[] args) {
//模拟回执
List<Receipt> receiptList = ReceiptBuilder.getReceiptList();
//策略上下文
ReceiptStrategyContext context = new ReceiptStrategyContext();
//策略模式最主要的工作: 将策略的 定义, 创建, 使用这三部分进行了解耦.
for (Receipt receipt : receiptList) {
//获取策略
ReceiptHandleStrategyFactory.init();
ReceiptHandleStrategy strategy = ReceiptHandleStrategyFactory.getStrategy(receipt.getType());
//设置策略
context.setReceiptHandleStrategy(strategy);
//执行策略
context.handleReceipt(receipt);
}
}
}
七 总结
**经过上面的改造,我们已经消除了if-else的结构,每当新来了一种回执,只需要添加新的回执处理策略,并修改ReceiptHandleStrategyFactory中的Map集合。如果要使得程序符合开闭原则,则需要调整ReceiptHandleStrategyFactory中处理策略的获取方式,通过反射的方式,获取指定包下的所有IReceiptHandleStrategy实现类,然后放到字典Map中去.**
### 6.3.5 策略模式总结
**1) 策略模式优点:**
* 策略类之间可以自由切换
由于策略类都实现同一个接口,所以使它们之间可以自由切换。
* 易于扩展
增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“
* 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。
**2) 策略模式缺点:**
* 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
* 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
**3) 策略模式使用场景**
* 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
> 策略模式最大的作用在于分离使用算法的逻辑和算法自身实现的逻辑,这样就意味着当我们想要优化算法自身的实现逻辑时就变得非常便捷,一方面可以采用最新的算法实现逻辑,另一方面可以直接弃用旧算法而采用新算法。使用策略模式能够很方便地进行替换。
* 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
> 在实际开发中,有许多算法可以实现某一功能,如查找、排序等,通过 if-else 等条件判断语句来进行选择非常方便。但是这就会带来一个问题:当在这个算法类中封装了大量查找算法时,该类的代码就会变得非常复杂,维护也会突然就变得非常困难。虽然策略模式看上去比较笨重,但实际上在每一次新增策略时都通过新增类来进行隔离,短期虽然不如直接写 if-else 来得效率高,但长期来看,维护单一的简单类耗费的时间其实远远低于维护一个超大的复杂类。
* 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
> 如果我们不希望客户知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关数据结构,可以提高算法的保密性与安全性.
**设计原则和思想其实比设计模式更加的普适和重要,掌握了代码的设计原则和思想,我们自然而然的就可以使用到设计模式,还有可能自己创建出一种新的设计模式.**