设计模式之策略模式
1.1 策略模式概述策略模式(strategy pattern)的原始定义是定义一系列算法将每一个算法封装起来并使它们可以相互替换。策略模式让算法可以独立于使用它的客户端而变化。其实我们在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况例如出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等。在软件开发中,经常会遇到这种情况,开发一个功能可以通过多个算法去实现,我们可以将所有的算法集中在一个类中,在这个类中提供多个方法,每个方法对应一个算法, 或者我们也可以将这些算法都封装在一个统一的方法中,使用if...else...等条件判断语句进行选择.但是这两种方式都存在硬编码的问题,后期需要增加算法就需要修改源代码,这会导致代码的维护变得困难.比如网购你可以选择工商银行、农业银行、建设银行等等但是它们提供的算法都是一致的就是帮你付款。在软件开发中也会遇到相似的情况当实现某一个功能存在多种算法或者策略我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。1.2 策略模式原理在策略模式中可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法,在这里每一个封装算法的类都可以被称为一种策略,为了保证这些策略在使用时具有一致性,一般会提供一个抽象的策略类来做算法的声明.而每种算法对应一个具体的策略类.策略模式的主要角色如下抽象策略Strategy类这是一个抽象角色通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。具体策略Concrete Strategy类实现了抽象策略定义的接口提供具体的算法实现或行为。环境或上下文Context类是使用算法的角色, 持有一个策略类的引用最终给客户端调用。1.3 策略模式实现策略模式的本质是通过Context类来作为中心控制单元对不同的策略进行调度分配。/* * 抽象策略类 * */ 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); } } /* * 上下文类策略模式的本质就是通过Context类作为控制单元,对不同的策略进行调度分配 * */ public class Context { //维持一个对抽象策略的引用 private Strategy strategy; public Context(Strategy strategy) { this.strategy strategy; } //调用策略中的算法 public void execute(){ strategy.algorithm(); } } public class TestStrategy { Test public void test01(){ Strategy strategyA new ConcreteStrategyA(); Context contextA new Context(strategyA);//可以在运行时指定类型,通过配置文件反射机制实现 contextA.execute(); System.out.println(); Strategy strategyB new ConcreteStrategyB(); Context contextB new Context(strategyB); contextB.execute(); } }1.4 策略模式应用实例面试问题: 如何用设计模式消除代码中的if-else物流行业中通常会涉及到EDI报文(XML格式文件)传输和回执接收每发送一份EDI报文后续都会收到与之关联的回执标识该数据在第三方系统中的流转状态。这里列举几种回执类型MT1101、MT2101、MT4101、MT8104系统在收到不同的回执报文后会执行对应的业务逻辑处理。我们就业回执处理为演示案例1 ) 不使用设计模式回执类/* * 回执类 * */ public class Receipt { private String message; private String type; public Receipt() { } public Receipt(String message, String type) { this.message message; this.type type; } public String getMessage() { return message; } public String getType() { return type; } }回执生成器/* * 回执信息生成类 * */ public class ReceiptBuilder { //模拟回执信息 public static ListReceipt getReceiptList(){ ListReceipt receiptList new ArrayList(); //回执类型MT1101、MT2101、MT4101、MT8104 /*receiptList.add(new Receipt(MT1101回执,MT1101)); receiptList.add(new Receipt(MT2101回执,MT2101)); receiptList.add(new Receipt(MT4101回执,MT4101));*/ receiptList.add(new Receipt(MT8104回执,MT8104)); //.... return receiptList; } }客户端/* * 测试类 * */ public class TestStrategy { Test public void test02(){ ListReceipt receiptList ReceiptBuilder.getReceiptList(); //回执类型MT1101、MT2101、MT4101、MT8104 for (Receipt receipt : receiptList) { if(MT1101.equals(receipt.getType())){ System.out.println(接收到MT1101回执); 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 \n); } } } }2 ) 使用策略模式进行优化通过策略模式, 将所有的if-else分支的业务逻辑抽取为各种策略类,让客户端去依赖策略接口,保证具体策略类的改变不影响客户端.策略接口/* * 抽象策略类接口 * */ public interface ReceiptStrategyHandle { void handleReceipt(Receipt receipt); }具体策略类/* * 具体策略类 * */ public class MT1101ReceiptStrategyHandle implements ReceiptStrategyHandle{ Override public void handleReceipt(Receipt receipt) { System.out.println(解析MT1101报文 receipt.getMessage()); } } public class MT2101ReceiptStrategyHandle implements ReceiptStrategyHandle{ Override public void handleReceipt(Receipt receipt) { System.out.println(解析MT2101报文 receipt.getMessage()); } }策略上下文类(策略接口的持有者)/* * 策略上下文类持有策略接口,决定执行哪一个具体的策略 * */ public class ReceiptStrategyContext { private ReceiptStrategyHandle receiptStrategyHandle; public void setReceiptStrategyHandle(ReceiptStrategyHandle receiptStrategyHandle) { this.receiptStrategyHandle receiptStrategyHandle; } //调用策略类中方法 public void getReceiptStrategyHandle(Receipt receipt){ if(null !receipt){ receiptStrategyHandle.handleReceipt(receipt); } } }策略工厂/* * 策略工厂类 * */ public class ReceiptStrategyHandleFactory { public ReceiptStrategyHandleFactory() { } //使用Map集合存储策略信息,彻底消除if...else private static MapString,ReceiptStrategyHandle strategyHandleMap; //初始化化具体策略,保存到map集合 public static void init(){ strategyHandleMap new HashMap(); strategyHandleMap.put(MT1101,new MT1101ReceiptStrategyHandle()); strategyHandleMap.put(MT2101,new MT2101ReceiptStrategyHandle()); } //根据回执类型获取对应策略类对象 public static ReceiptStrategyHandle getReceiptStrategyHandle(String receiptType) { return strategyHandleMap.get(receiptType); } }客户端/* * 测试类 * */ public class TestStrategy { Test public void client(){ //获取回执 ListReceipt receiptList ReceiptBuilder.getReceiptList(); //策略上下文 ReceiptStrategyContext context new ReceiptStrategyContext(); //初始化 ReceiptStrategyHandleFactory.init(); //策略模式最主要的工作将策略的定义创建使用这三部分进行类解耦 for (Receipt receipt : receiptList) { //获取策略 ReceiptStrategyHandle strategyHandle ReceiptStrategyHandleFactory.getReceiptStrategyHandle(receipt.getType()); //执行策略 strategyHandle.handleReceipt(receipt); } } }经过上面的改造我们已经消除了if-else的结构每当新来了一种回执只需要添加新的回执处理策略并修改ReceiptHandleStrategyFactory中的Map集合。如果要使得程序符合开闭原则则需要调整ReceiptHandleStrategyFactory中处理策略的获取方式通过反射的方式获取指定包下的所有IReceiptHandleStrategy实现类然后放到字典Map中去.//加入依赖dependency groupIdorg.dom4j/groupId artifactIddom4j/artifactId version2.1.3/version /dependency dependency groupIdjaxen/groupId artifactIdjaxen/artifactId version1.1.6/version /dependency创建config.xml?xml version1.0 encodingUTF-8 ? config className nameMT1101com.mashibing.strategy.example03.MT1101ReceiptStrategyHandle/className /config策略工厂类map集合优化/* * 策略工厂类 * */ public class ReceiptStrategyHandleFactory { public ReceiptStrategyHandleFactory() { } //使用Map集合存储策略信息,彻底消除if...else private static MapString,ReceiptStrategyHandle strategyHandleMap; //初始化化具体策略,保存到map集合 public static void init(){ strategyHandleMap new HashMap(); /* strategyHandleMap.put(MT1101,new MT1101ReceiptStrategyHandle()); strategyHandleMap.put(MT2101,new MT2101ReceiptStrategyHandle());*/ try { SAXReader reader new SAXReader(); //config文件路径 String file E:\\IDEA2025\\TestJavaEE\\msb-behavioral-pattern-06\\msb-behavioral-pattern-06\\src\\main\\resources\\config.xml; //解析xml文件获取文本 Document document reader.read(file); //通过文本获取node节点 Node node document.selectSingleNode(/config/className); //获取具体策略类全路径 String className node.getText(); //反射获取类的信息 Class clazz Class.forName(className); //实例化 ReceiptStrategyHandle strategy (ReceiptStrategyHandle) clazz.newInstance(); //放入map集合 strategyHandleMap.put(MT1101,strategy); } catch (Exception e) { throw new RuntimeException(e); } } //根据回执类型获取对应策略类对象 public static ReceiptStrategyHandle getReceiptStrategyHandle(String receiptType) { return strategyHandleMap.get(receiptType); } }1.5 策略模式总结1) 策略模式优点策略类之间可以自由切换由于策略类都实现同一个接口所以使它们之间可以自由切换。易于扩展增加一个新的策略只需要添加一个具体的策略类即可基本不需要改变原有的代码符合“开闭原则“避免使用多重条件选择语句if else充分体现面向对象设计思想。2) 策略模式缺点客户端必须知道所有的策略类并自行决定使用哪一个策略类。策略模式将造成产生很多策略类可以通过使用享元模式在一定程度上减少对象的数量。3) 策略模式使用场景一个系统需要动态地在几种算法中选择一种时可将每个算法封装到策略类中。策略模式最大的作用在于分离使用算法的逻辑和算法自身实现的逻辑这样就意味着当我们想要优化算法自身的实现逻辑时就变得非常便捷一方面可以采用最新的算法实现逻辑另一方面可以直接弃用旧算法而采用新算法。使用策略模式能够很方便地进行替换。一个类定义了多种行为并且这些行为在这个类的操作中以多个条件语句的形式出现可将每个条件分支移入它们各自的策略类中以代替这些条件语句。在实际开发中有许多算法可以实现某一功能如查找、排序等通过 if-else 等条件判断语句来进行选择非常方便。但是这就会带来一个问题当在这个算法类中封装了大量查找算法时该类的代码就会变得非常复杂维护也会突然就变得非常困难。虽然策略模式看上去比较笨重但实际上在每一次新增策略时都通过新增类来进行隔离短期虽然不如直接写 if-else 来得效率高但长期来看维护单一的简单类耗费的时间其实远远低于维护一个超大的复杂类。系统要求使用算法的客户不应该知道其操作的数据时可使用策略模式来隐藏与算法相关的数据结构。如果我们不希望客户知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关数据结构,可以提高算法的保密性与安全性.设计原则和思想其实比设计模式更加的普适和重要,掌握了代码的设计原则和思想,我们自然而然的就可以使用到设计模式,还有可能自己创建出一种新的设计模式.