用策略模式優化代碼的實例

實例一:

利用利用策略模式實際開發中 if else 條件判斷過多的問題,條件少還好,一旦 else if 過多這裏的邏輯將會比較混亂,並很容易出錯。
比如:在這裏插入圖片描述
剛開始條件較少,也就沒管那麼多直接寫的;現在功能多了導致每次新增一個 else 條件都得仔細覈對,生怕影響之前的邏輯。

重構之後這裏的結構如下:在這裏插入圖片描述
最後直接變爲兩行代碼,簡潔了許多。

而之前所有的實現邏輯都單獨抽取到其他實現類中。在這裏插入圖片描述拆分後的條件執行語句在這裏插入圖片描述
這樣每當需要新增一個 else 邏輯,只需要新增一個類實現同一個接口便可完成。每個處理邏輯都互相獨立互不干擾。在這裏插入圖片描述
按照目前的實現畫了一個草圖。

整體思路如下:

定義一個 InnerCommand 接口,其中有一個 process 函數交給具體的業務實現。
根據自己的業務,會有多個類實現 InnerCommand 接口;這些實現類都會註冊到 Spring Bean 容器中供之後使用。
通過客戶端輸入命令,從 Spring Bean 容器中獲取一個 InnerCommand 實例。
執行最終的 process 函數。
主要想實現的目的就是不在有多個判斷條件,只需要根據當前客戶端的狀態動態的獲取 InnerCommand 實例。

從源碼上來看最主要的就是 InnerCommandContext 類,他會根據當前客戶端命令動態獲取 InnerCommand 實例。在這裏插入圖片描述第一步是獲取所有的 InnerCommand 實例列表。
根據客戶端輸入的命令從第一步的實例列表中獲取類類型。
根據類類型從 Spring 容器中獲取具體實例對象。
因此首先第一步需要維護各個命令所對應的類類型。
在這裏插入圖片描述所以在之前的枚舉中就維護了命令和類類型的關係,只需要知道命令就能知道他的類類型。

這樣才能滿足只需要兩行代碼就能替換以前複雜的 if else,同時也能靈活擴展。

InnerCommand instance = innerCommandContext.getInstance(msg);
instance.process(msg) ;

注:如果是springboot工程在獲取上下文中的bean的時候可以通過如下方式:
方法一:

新建util類,實現ApplicationContextAware,重寫setApplicationContext方法。這個方式下,工具類也被註冊成了Bean,既然這樣,那就必須確保該類能被Spring自動掃描到。

@Component
public class SpringContextUtils implements ApplicationContextAware {
 
    private static ApplicationContext applicationContext;
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtils.applicationContext = applicationContext;
    }
 
    public static <T> T getBean(Class<T> cls) {
        if (applicationContext == null) {
            throw new RuntimeException("applicationContext注入失敗");
        }
        return applicationContext.getBean(cls);
    }
 
    public static Object getBean(String name) {
        if (applicationContext == null) {
            throw new RuntimeException("applicationContext注入失敗");
        }
        return applicationContext.getBean(name);
    }
 
    public static <T> T getBean(String name, Class<T> cls) {
        if (applicationContext == null) {
            throw new RuntimeException("applicationContext注入失敗");
        }
        return applicationContext.getBean(name, cls);
    }
 
    public static HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
            .getRequestAttributes();
        return requestAttributes == null ? null : requestAttributes.getRequest();
    }
}

方式二:

我想要的工具類,往往會部署在公共jar中,一般情況下不能被SpringBoot程序掃描到。所有就有手動注入上下文的方式。

@Slf4j
public class SpringUtils {
 
    private SpringUtils() {
    }
 
    private static ApplicationContext context = null;
 
    /**
     * 初始化Spring上下文
     *
     * @param ctx 上下文對象
     */
    public static void initContext(ApplicationContext ctx) {
        if(ctx == null) {
            log.warn("ApplicationContext is null.");
            return;
        }
        context = ctx;
    }
 
 
    /**
     * 根據類型獲取Bean
     *
     * @param cls Bean類
     * @param <T> Bean類型
     * @return Bean對象
     */
    public static <T> T getBean(Class<T> cls) {
        return context == null ? null : context.getBean(cls);
    }
 
    /**
     * 根據名稱獲取Bean
     *
     * @param name Bean名稱
     * @return Bean對象
     */
    public static Object getBean(String name) {
        return context == null ? null : context.getBean(name);
    }
 
    /**
     * 根據Bean名稱和類獲取Bean對象
     *
     * @param name Bean名稱
     * @param cls Bean類
     * @param <T> Bean類型
     * @return Bean對象
     */
    public static <T> T getBean(String name, Class<T> cls) {
        return context == null ? null : context.getBean(name, cls);
    }
}

此種方式不用實現ApplicationContextAware接口,但是需要手動設置上下文。所以在程序入口處,需要調用initContext方法,完成上下文的初始化。

public static void main(String[] args) {
    ApplicationContext context = SpringApplication.run(YCloudsServiceBApplication.class, args);
    SpringUtils.initContext(context);
}

下面展示springboot實例:

實例二:(Spring Boot 工程實例)

springUtil類如上已經建好,接口ContractStrategy和其實現類MainContractStrategy和RelieveContractStrategy類似上面實例,這裏不再粘出來。下面是Enum類:

public enum ContractValidateEnum {
  
  MAIN(1,MainContractStrategy.class,"主協議校驗"),
  RELIEVE(2,RelieveContractStrategy.class,"解除協議校驗");
  
  private int index;
  
  private Class<? extends ContractStrategy> strategy;
  
  private String info;
  
  ContractValidateEnum(int index,Class<? extends ContractStrategy> strategy, String info) {
    this.index = index;
    this.strategy = strategy;
    this.info = info;
  }
  
  /**
   * 根據index返回對應的enum
   *
   * @param index
   * @return
   */
  public static ContractValidateEnum fromIndex(int index) {
    for (ContractValidateEnum b : ContractValidateEnum.values()) {
      if (b.getIndex() == index) {
        return b;
      }
    }
    return MAIN;
  }
  
  /**
   * @return the index
   */
  public int getIndex() {
    return index;
  }


  /**
   * @return the info
   */
  public String getInfo() {
    return info;
  }

  /**
   * @return the strategy
   */
  public Class<?> getStrategy() {
    return strategy;
  }

}

然後是Context 類,他會根據當前客戶端命令動態獲取 strategy實例。

public class ContractValidateContext {
  
  public static ContractStrategy getStrategyInstance(ContractValidateEnum command){
    return (ContractStrategy) SpringUtil.getBean(command.getStrategy());
  }
}

這裏直接在Enum裏面放的Class<? extends ContractStrategy>可以簡化Context裏面代碼,當然也可以參照實例一里面放String類型的實現類的名字,再根據Class.forName()來實現。
在使用的時候直接這樣即可:

strategy = ContractValidateContext.getStrategyInstance(ContractValidateEnum.MAIN);
strategy.execValidate("參數");

注:本例主要是爲了解決簡單的策略模式不斷新建對象造成的開銷,這樣用springBean配合靜態Util類來管理實例,節省開銷,提高系統性能,簡化代碼。

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