设计模式之策略模式
定义
策略模式的用意是针对一组算法或逻辑,将每一个算法或逻辑封装到具有共同接口的独立的类中,从而使得它们之间可以相互替换。此模式让算法的变化,不会影响到使用算法的客户。算法本身是一种策略,而且这种策略是随时都可能相互替换的,这就是变化点,而封装变化点是面向对象的一种很重要的思维方式。策略模式是一种定义一系列算法的方式,从概念上看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
策略模式体现了这样两个原则——封装变化和对接口编程而不是对实现编程。
使用场景
- 多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。
- 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。
- 对客户隐藏具体策略(算法)的实现细节,彼此完全独立。
这种例子其实在我们的开发工作中也比较常见,比如支付方式:支付宝,微信,信用卡,储蓄卡等不同的支付方式支付逻辑肯定是不一样的;再比如拥有不同用户类型的网站:会员,超级会员和vip会员的购买价格或者其他的享受的待遇是不一样的;再如,很多框架中的负载均衡策略的实现,这些都是最佳的使用策略模式的地方。
角色构成
- 环境(Context)角色:持有一个公共策略接口的引用,直接给客户端调用。
- 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
- 具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。
优点
- 策略模式的Strategy类层次为Context定义了一些列的可供重用的算法或行为。继承有助于析取出这些算法的公共功能。
- 简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
- 当不同的行为堆砌在一个类中时,很难避免使用条件语句来选择合适的行为。比如你需要在客户端代码中来进行选择使用哪一种算法。将这些行为封装在一个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。策略模式就是用来封装算法的,但在实践中,我们发现可以用它来分装几乎任何类型的规则,只要在分析过程中听到需要在不同实践应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
缺点
- 在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象,这本身并没有解除客户端需要选择判断的压力(也就是说客户端需要知道所有的策略类型)。但是可以通过工厂模式进行一定程度的改进,这里需要注意的是如何将区分不同策略的标识传进来。
- 但策略类增加时,将可能产生大量的子类,如果一个算法被多次使用,但仅是外部条件不同的时候,可以考虑使用享元模式来进行优化,减少实例的个数,但这在涉及线程安全的时候需要格外注意。
与工厂模式的区别
工厂模式是创建型模式 ,它关注对象创建,提供创建对象的接口,让对象的创建与具体的使用客户无关。 策略模式是对象行为型模式 ,它关注行为和算法的封装 。再举个例子,还是我们出去旅游,对于策略模式我们只需要选择其中一种出行方法就好,但是工厂模式不同,工厂模式是你决定哪种旅行方案后,由工厂代替你去构建具体方案(工厂代替你去买火车票)。
与状态模式的区别
策略模式只是条件选择方法,只执行一次方法,而状态模式是随着状态的改变不停地更改执行方法。举个例子,就好比我们旅游,对于策略模式我们只需要选择其中一种出行方法就好了,但是状态模式不一样,可能我们到了A地点选择的是火车,到了B地点又选择飞机,根据不同的状态选择不同的出行方式。
代码
自定义注解
import java.lang.annotation.*; /** * 自定义策略模式注解 * <p> * * @Target:限制自定义注解可以作用的位置 * @Retention:指定此注解保留多长时间, RUNTIME:注解将被编译器记录在类文件中,在运行时保留VM,因此可以反读 * CLASS:注解将被编译器记录在类文件中,在运行时保留VM,因此可以反读。 * SOURCE:注解只在源代码级别保留,编译时被忽略 * @Documented:表明这个注解是由javadoc记录的,在默认情况下也有类似的记录工具。 如果一个类型声明被注解了文档化,它的注解成为公共API的一部分 * @Inherited:子类可以继承此注解 </P> * @date 2020/9/21 18:37 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface StrategyHandlerType { /** * 策略类型 * * @return */ int value(); }
抽象策略类
public interface Strategy { /** * 策略方法 */ void algorithmInterface(); }
具体策略类
import org.springframework.stereotype.Service; @Service @StrategyHandlerType(value = 1) public class ConcreteStrategyA implements Strategy { @Override public void algorithmInterface() { System.out.println("策略A方法"); } }
import org.springframework.stereotype.Service; @Service @StrategyHandlerType(value = 2) public class ConcreteStrategyB implements Strategy { @Override public void algorithmInterface() { System.out.println("策略B方法"); } }
策略处理器
import cn.hutool.core.lang.ClassScanner; import cn.hutool.core.map.MapUtil; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.stereotype.Component; import java.util.Map; /** * 策略处理类 * <p> * 之类是在项目启动时就要处理的 * </P> * * @author kuangxiang * @date 2020/9/21 18:51 */ @Component public class StrategyHandlerProcessor implements BeanFactoryPostProcessor { /** * 通过自定义注解扫描类 * * @param beanFactory * @throws BeansException */ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { Map<Integer, Class> handlerMap = MapUtil.newHashMap(); String packageName = "com.myself.demo.strategymodel";//需要扫描的包路径 ClassScanner.scanPackageByAnnotation(packageName, StrategyHandlerType.class).forEach(clazz -> { int value = clazz.getAnnotation(StrategyHandlerType.class).value(); handlerMap.put(value, clazz); }); StrategyHandlerContext strategyHandlerContext = new StrategyHandlerContext(handlerMap); beanFactory.registerSingleton(StrategyHandlerContext.class.getName(), strategyHandlerContext); } }
策略上下文
注意:这个类不需要加任何扫描用的注解例如,@Service,@Component
import com.myself.demo.utils.SpringContextUtil; import lombok.NoArgsConstructor; import java.util.Map; @NoArgsConstructor public class StrategyHandlerContext { private Map<Integer, Class> handlerMap; public StrategyHandlerContext(Map<Integer, Class> handlerMap) { this.handlerMap = handlerMap; } /** * 获取所需具体策略实现类的实例 * * @param type * @return */ public Strategy getInstance(Integer type) { Class clazz = handlerMap.get(type); if (clazz == null) { throw new IllegalArgumentException("没有对应的订单类型"); } return (Strategy) SpringContextUtil.getBean(clazz); } }
Spring上下文工具类
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * spring上下文工具类 * * @author kuangxiang * @date 2020/9/22 14:46 */ @Component public class SpringContextUtil implements ApplicationContextAware { /** * Spring应用上下文环境 */ private static ApplicationContext applicationContext; /** * 实现ApplicationContextAware接口的回调方法。设置上下文环境 * * @param applicationContext */ @Override public void setApplicationContext(ApplicationContext applicationContext) { SpringContextUtil.applicationContext = applicationContext; } /** * @return ApplicationContext */ public static ApplicationContext getApplicationContext() { return applicationContext; } /** * 获取对象 * * @param name * @return Object * @throws BeansException */ public static Object getBean(String name) throws BeansException { return applicationContext.getBean(name); } /** * 获取对象通过Class * * @param cls * @return Object * @throws BeansException */ public static <C> Object getBean(Class<C> cls) throws BeansException { return applicationContext.getBean(cls); } }
测试类
@ContextConfiguration() @SpringBootTest @Rollback(false) @RunWith(SpringRunner.class) public class StrategyTest { @Autowired private StrategyHandlerContext strategyHandlerContext; @Test public void test1() { Strategy instance = strategyHandlerContext.getInstance(3); instance.algorithmInterface(); } }