第六章 6.3 策略模式

分类:01_设计模式

标签:

6.3.1 策略模式概述

策略模式(strategy pattern)的原始定义是:定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。策略模式让算法可以独立于使用它的客户端而变化。

其实我们在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等。

111.jpg

在软件开发中,经常会遇到这种情况,开发一个功能可以通过多个算法去实现,我们可以将所有的算法集中在一个类中,在这个类中提供多个方法,每个方法对应一个算法, 或者我们也可以将这些算法都封装在一个统一的方法中,使用if...else...等条件判断语句进行选择.但是这两种方式都存在硬编码的问题,后期需要增加算法就需要修改源代码,这会导致代码的维护变得困难.

比如网购,你可以选择工商银行、农业银行、建设银行等等,但是它们提供的算法都是一致的,就是帮你付款。

110.jpg

在软件开发中也会遇到相似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。

6.3.2 策略模式原理

在策略模式中可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法,在这里每一个封装算法的类都可以被称为一种策略,为了保证这些策略在使用时具有一致性,一般会提供一个抽象的策略类来做算法的声明.而每种算法对应一个具体的策略类.

112.jpg

策略模式的主要角色如下:

6.3.3 策略模式实现

策略模式的本质是通过Context类来作为中心控制单元,对不同的策略进行调度分配。


/**
* 抽象策略类
* @author spikeCong
* @date 2022/10/13
**/
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");
   }
}

/**
* 环境类
* @author spikeCong
* @date 2022/10/13
**/
public class Context {

   //维持一个对抽象策略类的引用
   private Strategy strategy;

   public Context(Strategy strategy) {
       this.strategy = strategy;
   }

   //调用策略类中的算法
   public void algorithm(){
       strategy.algorithm();
   }
}

public class Client {

   public static void main(String[] args) {


       Strategy strategyA  = new ConcreteStrategyA();
       Context context = new Context(strategyA); //可以在运行时指定类型,通过配置文件+反射机制实现
       context.algorithm();
   }
}

6.3.4 策略模式应用实例

面试问题: 如何用设计模式消除代码中的if-else

物流行业中,通常会涉及到EDI报文(XML格式文件)传输和回执接收,每发送一份EDI报文,后续都会收到与之关联的回执(标识该数据在第三方系统中的流转状态)。

这里列举几种回执类型:MT1101、MT2101、MT4101、MT8104,系统在收到不同的回执报文后,会执行对应的业务逻辑处理。我们就业回执处理为演示案例

1 ) 不使用设计模式


/**
* 回执信息
* @author spikeCong
* @date 2022/10/13
**/
public class Receipt {

   private String message; //回执信息

   private String type; //回执类型(MT1101、MT2101、MT4101、MT8104)

   public Receipt() {
   }

   public Receipt(String message, String type) {
       this.message = message;
       this.type = type;
   }

   public String getMessage() {
       return message;
   }

   public void setMessage(String message) {
       this.message = message;
   }

   public String getType() {
       return type;
   }

   public void setType(String type) {
       this.type = type;
   }
}

public class ReceiptBuilder {

   public static List<Receipt> genReceiptList(){
       //模拟回执信息
       List<Receipt> receiptList = new ArrayList<>();
       receiptList.add(new Receipt("MT1101回执","MT1011"));
       receiptList.add(new Receipt("MT2101回执","MT2101"));
       receiptList.add(new Receipt("MT4101回执","MT4101"));
       receiptList.add(new Receipt("MT8104回执","MT8104"));

       //......
       return receiptList;
   }

}

public class Client {

   public static void main(String[] args) {

       List<Receipt> receiptList = ReceiptBuilder.genReceiptList();

       //循环判断
       for (Receipt receipt : receiptList) {
           if("MT1011".equals(receipt.getType())){
               System.out.println("接收到MT1011回执!");
               System.out.println("解析回执内容");
               System.out.println("执行业务逻辑A"+"\n");
           }else if("MT2101".equals(receipt.getType())){
               System.out.println("接收到MT2101回执!");
               System.out.println("解析回执内容");
               System.out.println("执行业务逻辑B"+"\n");
           }else if("MT4101".equals(receipt.getType())) {
               System.out.println("接收到MT4101回执!");
               System.out.println("解析回执内容");
               System.out.println("执行业务逻辑C"+"\n");
           }else if("MT8104".equals(receipt.getType())) {
               System.out.println("接收到MT8104回执!");
               System.out.println("解析回执内容");
               System.out.println("执行业务逻辑D");
           }

           //......
       }
   }
}

2 ) 使用策略模式进行优化

通过策略模式, 将所有的if-else分支的业务逻辑抽取为各种策略类,让客户端去依赖策略接口,保证具体策略类的改变不影响客户端.


/**
* 回执处理策略接口
* @author spikeCong
* @date 2022/10/13
**/
public interface ReceiptHandleStrategy {

   void handleReceipt(Receipt receipt);
}

public class Mt1011ReceiptHandleStrategy implements ReceiptHandleStrategy {

   @Override
   public void handleReceipt(Receipt receipt) {
       System.out.println("解析报文MT1011: " + receipt.getMessage());
   }
}

public class Mt2101ReceiptHandleStrategy implements ReceiptHandleStrategy {

   @Override
   public void handleReceipt(Receipt receipt) {
       System.out.println("解析报文MT2101: " + receipt.getMessage());
   }
}

......

/**
* 上下文类,持有策略接口
* @author spikeCong
* @date 2022/10/13
**/
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);
       }
   }
}

public class ReceiptHandleStrategyFactory {

   public ReceiptHandleStrategyFactory() {
   }

   //使用Map集合存储策略信息,彻底消除if...else
   private static Map<String,ReceiptHandleStrategy> strategyMap;

   //初始化具体策略,保存到map集合
   public static void init(){
       strategyMap = new HashMap<>();
       strategyMap.put("MT1011",new Mt1011ReceiptHandleStrategy());
       strategyMap.put("MT2101",new Mt2101ReceiptHandleStrategy());
   }

   //根据回执类型获取对应策略类对象
   public static ReceiptHandleStrategy getReceiptHandleStrategy(String receiptType){
       return strategyMap.get(receiptType);
   }
}

public class Client {

   public static void main(String[] args) {

       //模拟回执
       List<Receipt> receiptList = ReceiptBuilder.genReceiptList();


       //策略上下文
       ReceiptStrategyContext context = new ReceiptStrategyContext();

       //策略模式将策略的 定义、创建、使用这三部分进行了解耦
       for (Receipt receipt : receiptList) {
           //获取置策略
           ReceiptHandleStrategyFactory.init();
           ReceiptHandleStrategy strategy = ReceiptHandleStrategyFactory.getReceiptHandleStrategy(receipt.getType());
           //设置策略
           context.setReceiptHandleStrategy(strategy);
           //执行策略
           context.handleReceipt(receipt);
       }
   }
}

经过上面的改造,我们已经消除了if-else的结构,每当新来了一种回执,只需要添加新的回执处理策略,并修改ReceiptHandleStrategyFactory中的Map集合。如果要使得程序符合开闭原则,则需要调整ReceiptHandleStrategyFactory中处理策略的获取方式,通过反射的方式,获取指定包下的所有IReceiptHandleStrategy实现类,然后放到字典Map中去.

6.3.5 策略模式总结

1) 策略模式优点:

2) 策略模式缺点:

3) 策略模式使用场景

设计原则和思想其实比设计模式更加的普适和重要,掌握了代码的设计原则和思想,我们自然而然的就可以使用到设计模式,还有可能自己创建出一种新的设计模式.


修改内容