策略模式獲取算法族(行爲)的方式

今天總結一下策略模式使用過程中獲取算法族(行爲)的方式,內容純屬個人觀點,歡迎指正。

一、策略模式的場景及難點

1、策略模式的定義及說明在此就不提了,沒有基礎的推薦大家一本書《Head First 設計模式》,圖文及場景的選擇讓讀者很容易理解。
個人的使用場景:

項目中有多個執行器,每個執行器執行不同的業務處理,希望將這些執行器做統一的入口,根據入口的行爲參數決定使用哪個執行器去執行相應的任務,在策略模式的使用中,個人覺得這也是難點所在,想到的做法有三種,通過Spring getBean、Java的反射以及另一種在getBean的基礎上增加Spring Annotation,下面給逐一介紹三種方式的使用。

二、策略模式獲取處理類的方式

1、Spring getBean

a.執行器的入口

/**
 * @author xiaokaige
 */
@RestController
@RequestMapping(value = "/test")
public class TestController {

    @Autowired
    private ExecutorStrategyFactory strategyFactory;

    @GetMapping("strategy")
    public void strategy(){
        //創建具體的執行策略,並執行爲
        ExecutorStrategyInterface strategy = strategyFactory.createStrategy("executorB");
        strategy.doExecutorAction();
    }
}

b.創建具體的執行策略

1、通過@PostConstruct修飾init(),在服務器加載Servle的時候運行,並且只會被服務器執行一次。
2、通過applicationContext.getBeansOfType(ExecutorStrategyInterface.class)獲取所有實現ExecutorStrategyInterface的Bean,將其放入內存EXECUTOR_BEANS。
3、遍歷實現ExecutorStrategyInterface的Bean並返回與參數對應的實例。


    /**
 * @author xiaokaige
 */
@Component
public class ExecutorStrategyFactory {
    private static final Map<String,ExecutorStrategyInterface> EXECUTOR_BEANS = Maps.newConcurrentMap();
    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 根據不同的執行器創建不同的策略
     * @return
     */
    public ExecutorStrategyInterface createStrategy(String executor) {
        Optional<ExecutorStrategyInterface> strategyOptional =
                EXECUTOR_BEANS
                        .entrySet()
                        .stream()
                        .map(e -> {
                            if (Objects.equals(e.getKey(),executor)) {
                                return e.getValue();
                            }
                            return null;
                        }).filter(Objects::nonNull).findAny();
        if(strategyOptional.isPresent()){
            System.out.println(strategyOptional.get());
            return strategyOptional.get();
        }
        throw new RuntimeException("策略獲得失敗");
    }

    /**
     * 初始化策略列表
     */
    @PostConstruct
    private void init() {
        EXECUTOR_BEANS.putAll(applicationContext.getBeansOfType(ExecutorStrategyInterface.class));
    }

}

c.執行器接口

/**
 * @author xiaokaige
 */
public interface ExecutorStrategyInterface {
    /**
     * 執行器接口
     */
    void doExecutorAction();

}

d.執行器接口的實現

/**
 * @author xiaokaige
 */
@Slf4j
@Service
public class ExecutorA implements ExecutorStrategyInterface {

    @Override
    public void doExecutorAction() {
        log.info("我是通過策略A進來的");
    }
}
/**
 * @author xiaokaige
 */
@Slf4j
@Service
public class ExecutorB implements ExecutorStrategyInterface {

    @Override
    public void doExecutorAction() {
        log.info("我是通過策略B進來的");
    }
}

e.請求接口

請求接口可以看到控制檯輸出:I entered through Strategy B.

clipboard.png

補充一點,此方式也可使用@Service("別名"),此時applicationContext.getBeansOfType(ExecutorStrategyInterface.class)拿到的是“別名”

2、Java反射

這種方式非常簡單,不需要策略工廠類,直接在調用的時候通過全類名及方法名利用java反射調用方法就好了。

/**
 * @author xiaokaige
 */
@RestController
@RequestMapping(value = "/test")
public class TestController {

    @GetMapping("strategy")
    public void strategy(){
        try {
            Class c = Class.forName("com.xiaokaige.demo.strategy.ExecutorB");
            Method m = c.getMethod("doExecutorAction");
            m.invoke(c.newInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

控制檯輸出結果如下:

clipboard.png

3、Spring Annotation

a.定義 @Annotation

首先通過@interface Annotation{ } 定義一個註解 @Annotation

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExecutorStrategyAnnotation {
    /**
     * 執行器id,默認值1
     */
    int executorId() default 1;
}

b.爲實現ExecutorStrategyInterface接口的類添加一個Id

/**
 * @author xiaokaige
 */
@Slf4j
@Service
@ExecutorStrategyAnnotation(executorId = ExecutorStrategyConstants.A)
public class ExecutorA implements ExecutorStrategyInterface {

    @Override
    public void doExecutorAction() {
        log.info("I entered through Strategy A.");
    }
}
/**
 * @author xiaokaige
 */
@Slf4j
@Service
@ExecutorStrategyAnnotation(executorId = ExecutorStrategyConstants.B)
public class ExecutorB implements ExecutorStrategyInterface {

    @Override
    public void doExecutorAction() {
        log.info("I entered through Strategy B.");
    }
}
/**
 * @author xiaokaige
 */
public class ExecutorStrategyConstants {
    public static final int B = 2;
    public static final int A = 3;
    public static final int C =4;
}

c.執行器的入口

這裏創建策略的參數有執行器的名稱換爲策略的ID

/**
 * @author xiaokaige
 */
@RestController
@RequestMapping(value = "/test")
public class TestController {

    @Autowired
    private ExecutorStrategyFactory strategyFactory;

    @GetMapping("strategy")
    public void strategy(){
        //創建具體的執行策略,並執行爲
        ExecutorStrategyInterface strategy = strategyFactory.createStrategy(3);
        strategy.doExecutorAction();
    }
}

d.策略工廠獲實現

ExecutorStrategyInterface的每個Bean的Id,返回與傳參對應的策略

/**
 * @author xiaokaige
 */
@Component
public class ExecutorStrategyFactory {
    private static final Map<String,ExecutorStrategyInterface> EXECUTOR_BEANS = Maps.newConcurrentMap();
    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 根據不同的執行器創建不同的策略
     * @return
     */
    public ExecutorStrategyInterface createStrategy(int executor) {
        Optional<ExecutorStrategyInterface> strategyOptional =
                EXECUTOR_BEANS
                        .entrySet()
                        .stream()
                        .map(e -> {
                            ExecutorStrategyAnnotation validExecutor = e.getValue().getClass().getDeclaredAnnotation(ExecutorStrategyAnnotation.class);
                            if (Objects.equals(validExecutor.executorId(),executor)) {
                                return e.getValue();
                            }
                            return null;
                        }).filter(Objects::nonNull).findFirst();
        if(strategyOptional.isPresent()){
            System.out.println(strategyOptional.get());
            return strategyOptional.get();
        }
        throw new RuntimeException("策略獲得失敗");
    }

    /**
     * 初始化策略列表
     */
    @PostConstruct
    private void init() {
        EXECUTOR_BEANS.putAll(applicationContext.getBeansOfType(ExecutorStrategyInterface.class));
    }

}

ExecutorStrategyInterface類無變化

e.運行程序控制臺輸出:

clipboard.png

到此三種方式介紹結束。

三、總結

第一種方式調用方只需要提供執行器的名稱,還可以通過別名靈活配置,但是要實現策略工廠。
第二種通過java反射最爲簡單,不需要策略工廠類,但是參數需要提供類名及方法名,需要設置兩個參數。
第三種方式與第一種類似,只是多了Id的映射,適合的場景與第一種方式不同。
個人建議使用第一種,具體看大家的使用了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章