Java 設計模式綜合運用(門面+模版方法+責任鏈+策略+工廠方法)

在上一篇文章 Java設計模式綜合運用(門面+模版方法+責任鏈+策略)中,筆者寫了一篇門面模式、模版方法、責任鏈跟策略模式的綜合運用的事例文章,可是後來筆者發現,在實現策略模式的實現上,發現了一個弊端:那就是若是在後續業務發展中,須要再次增長一個業務策略的時候,則須要再次繼承 AbstractValidatorHandler類(詳情請參見上篇文章),這樣就會形成必定的類膨脹。今天我利用註解的方式改形成動態策略模式,這樣就只須要關注本身的業務類便可,無需再實現一個相似的Handler類。
本文也同步發佈至簡書,地址: https://www.jianshu.com/p/b86...

1. 項目背景

1.1 項目簡介

在公司的一個業務系統中,有這樣的一個需求,就是根據不一樣的業務流程,能夠根據不一樣的組合主鍵策略進行動態的數據業務查詢操做。在本文中,我假設有這樣兩種業務,客戶信息查詢和訂單信息查詢,對應如下枚舉類:java

/**
 * 業務流程枚舉
 * @author landyl
 * @create 11:18 AM 05/07/2018
 */
public enum WorkflowEnum {
    ORDER(2),
    CUSTOMER(3),
    ;
    ....
}

每種業務類型都有本身的組合主鍵查詢規則,而且有本身的查詢優先級,好比客戶信息查詢有如下策略:spring

  1. customerId
  2. requestId
  3. birthDate+firstName

以上僅是假設性操做,實際業務規則比這複雜的多segmentfault

1.2 流程梳理

主要業務流程,能夠參照如下簡單的業務流程圖。設計模式

1.2.1 查詢抽象模型

查詢抽象模型.png

1.2.2 組合主鍵查詢策略

組合主鍵查詢策略.png

1.2.3 組合主鍵查詢責任鏈

組合主鍵查詢責任鏈.png

2. Java註解簡介

註解的語法比較簡單,除了@符號的使用以外,它基本與Java固有語法一致。數組

2.1 元註解

JDK1.5提供了4種標準元註解,專門負責新註解的建立。ide

註解 說明
@Target 表示該註解能夠用於什麼地方,可能的ElementType參數有:<br/>CONSTRUCTOR:構造器的聲明<br/>FIELD:域聲明(包括enum實例)<br/>LOCAL_VARIABLE:局部變量聲明<br/>METHOD:方法聲明<br/>ACKAGE:包聲明<br/>PARAMETER:參數聲明<br/>TYPE:類、接口(包括註解類型)或enum聲明
@Retention 表示須要在什麼級別保存該註解信息。可選的RetentionPolicy參數包括:<br/>SOURCE:註解將被編譯器丟棄<br/>CLASS:註解在class文件中可用,但會被VM丟棄<br/>RUNTIME:JVM將在運行期間保留註解,所以能夠經過反射機制讀取註解的信息。
@Document 將註解包含在Javadoc中
@Inherited 容許子類繼承父類中的註解

2.2 自定義註解

定義一個註解的方式至關簡單,以下代碼所示:優化

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
//使用@interface關鍵字定義註解
public @interface Description {
    /*
     * 註解方法的定義(其實在註解中也能夠看作成員變量)有以下的規定:
     * 1.不能有參數和拋出異常
     * 2.方法返回類型只能爲八種基本數據類型和字符串,枚舉和註解以及這些類型構成的數組
     * 3.能夠包含默認值,經過default實現
     * 4.若是隻有一個方法(成員變量),最好命名爲value
     */
    String value();
    int count() default 1; //默認值爲1
}

註解的可用的類型包括如下幾種:全部基本類型、String、Class、enum、Annotation、以上類型的數組形式。元素不能有不肯定的值,即要麼有默認值,要麼在使用註解的時候提供元素的值。並且元素不能使用null做爲默認值。註解在只有一個元素且該元素的名稱是value的狀況下,在使用註解的時候能夠省略「value=」,直接寫須要的值便可。this

2.3 使用註解

如上所示的註解使用以下:spa

/**
 * @author landyl
 * @create 2018-01-12:39 PM
 */
//在類上使用定義的Description註解
@Description(value="class annotation",count=2)
public class Person {
    private String name;
    private int age;

    //在方法上使用定義的Description註解
    @Description(value="method annotation",count=3)
    public String speak() {
        return "speaking...";
    }
}

使用註解最主要的部分在於對註解的處理,那麼就會涉及到註解處理器。從原理上講,註解處理器就是經過反射機制獲取被檢查方法上的註解信息,而後根據註解元素的值進行特定的處理。設計

/**
 * @author landyl
 * @create 2018-01-12:35 PM
 * 註解解析類
 */
public class ParseAnnotation {
    public static void main(String[] args){
        //使用類加載器加載類
        try {
            Class c = Class.forName("com.annatation.Person");//加載使用了定義註解的類
            //找到類上的註解
            boolean isExist = c.isAnnotationPresent(Description.class);
            if(isExist){
                //拿到註解示例
                Description d = (Description)c.getAnnotation(Description.class);
                System.out.println(d.value());
            }
            //找到方法上的註解
            Method[] ms = c.getMethods();
            for(Method m : ms){
                boolean isMExist = m.isAnnotationPresent(Description.class);
                if(isMExist){
                    Description d = m.getAnnotation(Description.class);
                    System.out.println(d.value());
                }
            }
            //另一種註解方式
            for(Method m:ms){
                Annotation[] as = m.getAnnotations();
                for(Annotation a:as){
                    if(a instanceof Description){
                        Description d = (Description)a;
                        System.out.println(d.value());
                    }
                }

            }
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

3. 策略模式升級版

3.1 策略模式實現方式

  1. 使用工廠進行簡單的封裝
  2. 使用註解動態配置策略
  3. 使用模版方法模式配置策略(參見Java設計模式綜合運用(門面+模版方法+責任鏈+策略))
  4. 使用工廠+註解方式動態配置策略(利用Spring加載)

其中第一、2點請參見org.landy.strategy 包下的demo事例便可,而第4點的方式其實就是結合第一、二、3點的優勢進行整合的方式。

3.2 註解方式優勢

使用註解方式能夠極大的減小使用模版方法模式帶來的擴展時須要繼承模版類的弊端,工廠+註解的方式能夠無需關心其餘業務類的實現,並且減小了類膨脹的風險。

3.3 組合主鍵查詢策略

本文以組合主鍵查詢策略這一策略進行說明,策略註解以下:

/**
 * 組合主鍵查詢策略(根據不一樣業務流程區分組合主鍵查詢策略,而且每一個業務流程都有本身的優先級策略)
 * @author landyl
 * @create 2:22 PM 09/29/2018
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface KeyIdentificationStrategy {

    /**
     * 主鍵策略優先級
     * @return
     */
    int priority() default 0;
    /**
     * 業務流程類型(如:訂單信息,會員信息等業務流程)
     * @return
     */
    WorkflowEnum workflowId();
    /**
     * the spring bean name
     * @return
     */
    String beanName();

}

3.4 策略工廠

既然定義了組合主鍵查詢策略註解,那必然須要一個註解處理器進行解析註解的操做,本文以工廠的方式進行。主要邏輯以下:

  1. 掃描指定包下的Java類,找出相應接口(即KeyIdentification)下的全部Class對象。

    private List<Class<? extends KeyIdentification>> getIdentifications() {
            Set<String> packageNames = this.getBasePackages();
            List<Class<? extends KeyIdentification>> identifications = new ArrayList<>();
            if(packageNames != null) {
                packageNames.forEach((packageName) -> identifications.addAll(getIdentifications(packageName)));
            }
            return identifications;
        }
  2. 解析註解KeyIdentificationStrategy,定義一個排序對象(KeyIdentificationComparator),指定優先級。

    /**
         * define a comparator of the KeyIdentification object through the priority of the IdentifyPriority for sort purpose.
*/

private class KeyIdentificationComparator implements Comparator {

@Override
   public int compare(Object objClass1, Object objClass2) {
       if(objClass1 != null && objClass2 != null) {
           Optional<KeyIdentificationStrategy> strategyOptional1 = getPrimaryKeyIdentificationStrategy((Class)objClass1);
           Optional<KeyIdentificationStrategy> strategyOptional2 = getPrimaryKeyIdentificationStrategy((Class)objClass2);

           KeyIdentificationStrategy ip1 = strategyOptional1.get();
           KeyIdentificationStrategy ip2 = strategyOptional2.get();

           Integer priority1 = ip1.priority();
           Integer priority2 = ip2.priority();

           WorkflowEnum workflow1 = ip1.workflowId();
           WorkflowEnum workflow2 = ip2.workflowId();
           //先按業務類型排序
           int result = workflow1.getValue() - workflow2.getValue();
           //再按優先級排序
           if(result == 0) return priority1.compareTo(priority2);

           return result;
       }
       return 0;
   }

}

3. 根據註解,把相應業務類型的組合主鍵查詢策略對象放入容器中(即`DefaultKeyIdentificationChain`)。

KeyIdentificationStrategy strategy = strategyOptional.get();

String beanName = strategy.beanName();
               //業務流程類型
               WorkflowEnum workflowId = strategy.workflowId();
               KeyIdentificationStrategy priority = getPrimaryKeyIdentificationStrategy(v).get();
               LOGGER.info("To add identification:{},spring bean name is:{},the identify priority is:{},workflowId:{}",simpleName,beanName,priority.priority(),workflowId.name());
               KeyIdentification instance = ApplicationUtil.getApplicationContext().getBean(beanName,v);

defaultKeyIdentificationChain.addIdentification(instance,workflowId);

4. 後續,在各自對應的業務查詢組件對象中便可使用該工廠對象調用以下方法,便可進行相應的查詢操做。

public IdentificationResultType identify(IdentifyCriterion identifyCriterion,WorkflowEnum workflowId) {

//must set the current workflowId
    defaultKeyIdentificationChain.doClearIdentificationIndex(workflowId);
    return defaultKeyIdentificationChain.doIdentify(identifyCriterion,workflowId);
}
## 4. 總結

以上就是本人在實際工做中,對第一階段使用到的設計模式的一種反思後獲得的優化結果,可能還有各類不足,可是我的感受仍是有改進,但願你們也不吝賜教,你們一塊兒進步纔是真理。
相關文章
相關標籤/搜索