定义

  策略模式的用意是针对一组算法或逻辑,将每一个算法或逻辑封装到具有共同接口的独立的类中,从而使得它们之间可以相互替换。此模式让算法的变化,不会影响到使用算法的客户。算法本身是一种策略,而且这种策略是随时都可能相互替换的,这就是变化点,而封装变化点是面向对象的一种很重要的思维方式。策略模式是一种定义一系列算法的方式,从概念上看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。

  策略模式体现了这样两个原则——封装变化和对接口编程而不是对实现编程。

使用场景

  • 多个类只区别在表现行为不同,可以使用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();
    }
}

 

版权声明:本文为htyj原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/htyj/p/12844746.html