策略模式(Strategy)从浅到深
version1:策略模式(Strategy)
概念
定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
代码实现
目录结构:
具体代码:
Client类调用不同的具体策略,只需要调用同一个concreteStrategy类传入不同的具体策略方法。
package controller; import service.ConcreteStrategy; import service.Impl.ConcreteStrategyA; import service.Impl.ConcreteStrategyB; import service.Impl.ConcreteStrategyC; /** * @author SHshuo * @data 2021/10/16--8:41 * 策略模式 */ public class Client { public static void main(String[] args) { ConcreteStrategy concreteStrategy; // 实例化的时候传入不同的具体策略对象 concreteStrategy = new ConcreteStrategy(new ConcreteStrategyA()); concreteStrategy.AlgorithmInterface(); concreteStrategy = new ConcreteStrategy(new ConcreteStrategyB()); concreteStrategy.AlgorithmInterface(); concreteStrategy = new ConcreteStrategy(new ConcreteStrategyC()); concreteStrategy.AlgorithmInterface(); } }
ConcreteStrategy类面向接口(Strategy类)编程、维护一个对Strategy对象的引用。
package Context; /** * @author SHshuo * @data 2021/10/16--8:42 * 面向接口编程、体现依赖倒转原则 * 上下文 */ public class ConcreteStrategy { private Strategy strategy; // 初始化的之后传入具体的策略对象 public ConcreteStrategy(Strategy strategy){ this.strategy = strategy; } // 根据具体的策略对象、调用其算法的方法 public void AlgorithmInterface(){ strategy.AlgorithmInterface(); } }
package service; /** * @author SHshuo * @data 2021/10/16--8:35 * 如果有类实现不了Strategy,可以使用委托的方式添加AlgorithmInterface方法到Client类中 */ public interface Strategy { void AlgorithmInterface(); }
具体实现Strategy接口的策略方法、ConcreteStrategyA类、ConcreteStrategyB类、ConcreteStrategyC类大体一致
package service.Impl; import service.Strategy; /** * @author SHshuo * @data 2021/10/16--8:36 */ public class ConcreteStrategyA implements Strategy { @Override public void AlgorithmInterface() { System.out.println("A算法实现具体内容"); } }
UML类图
- 三个具体的实现类(ConcreteStrategyA)实现接口(Strategy);
- Context维护Strategy对象的引用,构造函数传入具体的实现类,并赋给成员变量,成员变量调用具体的函数;
- Client调用Context类,传入具体的实现类。
好处
- 解决客户端大量if else的分支(将这些分支的行为封装在一个个独立的Strategy类中,可以在使用这些行为类来消除条件语句);
- 简化了单元测试(每个算法都有自己的类,可以通过自己的接口单独测试);
问题
客户端去判断具体的实现类,增加了客户端的压力,没有充分与具体的实现类进行解耦;
Version2:引入工厂模式
针对客户端的改进,将改动部分移动到Context,使得客户端的代码不需要改动;
- 使用工厂模式,将客户端的代码判断具体的实现类移动到Context;
- 客户端传入type,对应具体的实现类;
- Context的构造函数,将传入的type进行分支的判断,初始化不同的具体实现类。
改进代码:
Client(不需要改动)
package controller; import Context.ConcreteStrategy; import service.Impl.ConcreteStrategyA; import service.Impl.ConcreteStrategyB; import service.Impl.ConcreteStrategyC; /** * @author SHshuo * @data 2021/10/16--8:41 * 策略模式与工厂模式结合 */ public class Client { public static void main(String[] args) { ConcreteStrategy concreteStrategy; /** * 客户端传入不同的参数、例如传入的是下拉框的选项,这里写一个就够了,也就是说客户端代码是不变的; * 减轻客户端的职责交给Context,实现了具体的策略类与客户端解耦。 * * 单纯的策略模式,这里仍需要自己定义具体实现的策略,每次仍需要添加分支,修改客户端的代码,增加客户端的压力 */ concreteStrategy = new ConcreteStrategy("ConcreteStrategyA"); concreteStrategy.AlgorithmInterface(); // concreteStrategy = new ConcreteStrategy("ConcreteStrategyB"); // concreteStrategy.AlgorithmInterface(); // // concreteStrategy = new ConcreteStrategy("ConcreteStrategyC"); // concreteStrategy.AlgorithmInterface(); } }
Context
package Context; import service.Impl.ConcreteStrategyA; import service.Impl.ConcreteStrategyB; import service.Impl.ConcreteStrategyC; import service.Strategy; /** * @author SHshuo * @data 2021/10/16--8:42 * 简单工厂模式 * 由Context作为工厂,创建具体的实现类 */ public class ConcreteStrategy { private Strategy strategy; /** * 利用工厂模式在构造函数中根据不同分支创建具体的对象, * 而不需要从客户端传入具体的对象,实现了客户端与具体的实现类的解耦 * @param type */ public ConcreteStrategy(String type){ switch (type){ case"ConcreteStrategyA": strategy = new ConcreteStrategyA(); break; case"ConcreteStrategyB": strategy = new ConcreteStrategyB(); break; case"ConcreteStrategyC": strategy = new ConcreteStrategyC(); break; } } // 根据具体的策略对象、调用其算法的方法 public void AlgorithmInterface(){ strategy.AlgorithmInterface(); } }
好处
- 增加具体实现类的时候,由于不需要传入具体的实现类,所以客户端代码不用改变;
- 同时充分与具体实现类解耦,减轻了客户端的职责,交给Context的工厂完成创建对应具体实现类的对象。
问题
- 增加具体实现类的时候,仍需要改动Context的代码,需要修改分支增加对应分支的行为。
version3:引入反射
针对Context类的改进,利用反射;使得Context类的代码不需要改动;
- 利用反射解决Context类的分支问题;
- 这样增加一个具体的实现类的时候,只需要实现对应的接口即可;客户端、Context类的代码都不需要改变。
改进代码:
Context
package Context; import service.Strategy; import java.lang.reflect.Constructor; /** * @author SHshuo * @data 2021/10/16--8:42 * 使用反射实例化具体的类 * 不需要改动Context的代码 */ public class ConcreteStrategy { private Strategy strategy; /** * 利用反射根据类名,创建对应具体类的实例, * 不需要Switch,case分支进行传入判断,而不需要改动Context类的代码 * @param type */ public ConcreteStrategy(String type){ try { Class c = Class.forName("service.Impl." + type); Constructor constructor = c.getConstructor(); strategy = (Strategy) constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } } // 根据具体的策略对象、调用其算法的方法 public void AlgorithmInterface(){ strategy.AlgorithmInterface(); } }
好处
- 增加具体实现类的时候,只需要实现对应的接口即可;客户端、Context类的代码都不需要改变;
补充springboot获取bean的方式
通过实现ApplicationContextAware接口,创建ApplicationContext对象,调用里面的getBean方法获取到bean。
ApplicationContext app = new ApplicationContext(); Strategy strategy = (Strategy)app.getBean("类名");
应用:
策略模式感觉类似于controller层注入数据的时候,都是注入使用的接口,但是对于多个类实现一个接口的时候,需要在注入的时候指明哪个类
@Autowired + @Qualifier("hshuo") 相当于@Resource(name="hshuo")
参考:
- 大话设计模式这本书,写的很幽默;
源代码:
hshuo的面试之路 文章被收录于专栏
作者目标是找到一份Java后端方向的工作 此专栏用来记录从Bilibili、书本、其他优质博客上面学习的内容 用于巩固、总结内容 主要包含Docker、Dubbo、Java基础、JUC、Maven、MySQL、Redis、SpringBoot、SpringCloud、数据结构、杂文、算法、计算机网络、操作系统、设计模式等相关内容