重學 Java 設計模式:實戰組合模式(營銷差異化人群發券,決策樹引擎搭建場景)

小傅哥發表於2020-06-08

作者:小傅哥
部落格:https://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收穫!?

一、前言

小朋友才做選擇題,成年人我都要

頭幾年只要群裡一問我該學哪個開發語言,哪個語言最好。群裡肯定聊的特別火熱,有人支援PHP、有人喊號Java、也有C++和C#。但這幾年開始好像大家並不會真的刀槍棍棒、斧鉞鉤叉般討論了,大多數時候都是開玩笑的鬧一鬧。於此同時在整體的網際網路開發中很多時候是一些開發語言公用的,共同打造整體的生態圈。而大家選擇的方式也是更偏向於不同領域下選擇適合的架構,而不是一味地追求某個語言。這可以給很多初學程式設計的新人一些提議,不要刻意的覺得某個語言好,某個語言不好,只是在適合的場景下選擇最需要的。而你要選擇的那個語言可以參考招聘網站的需求量和薪資水平決定。

程式設計開發不是炫技

總會有人喜歡在整體的專案開發中用上點新特性,把自己新學的知識實踐試試。不能說這樣就是不好,甚至可以說這是一部分很熱愛學習的人,喜歡創新,喜歡實踐。但程式設計除了用上新特性外,還需要考慮整體的擴充套件性、可讀性、可維護、易擴充套件等方面的考慮。就像你家裡僱傭了一夥裝修師傅,有那麼一個小工喜歡炫技搞花活,在家的淋浴下?安裝了馬桶?。

即使是寫CRUD也應該有設計模式

往往很多大需求都是通過增刪改查堆出來的,今天要一個需求if一下,明天加個內容else擴充套件一下。日積月累需求也就越來越大,擴充套件和維護的成本也就越來越高。往往大部分研發是不具備產品思維和整體業務需求導向的,總以為寫好程式碼完成功能即可。但這樣的不考慮擴充套件性的實現,很難讓後續的需求都快速迭代,久而久之就會被陷入惡性迴圈,每天都有bug要改。

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三個,可以通過關注公眾號bugstack蟲洞棧,回覆原始碼下載獲取(開啟獲取的連結,找到序號18)
工程 描述
itstack-demo-design-8-01 使用一坨程式碼實現業務需求
itstack-demo-design-8-02 通過設計模式優化改造程式碼,產生對比性從而學習

三、組合模式介紹

組合模式,圖片來自 refactoringguru.cn

從上圖可以看到這有點像螺絲?和螺母,通過一堆的連結組織出一棵結構樹。而這種通過把相似物件(也可以稱作是方法)組合成一組可被呼叫的結構樹物件的設計思路叫做組合模式。

這種設計方式可以讓你的服務組節點進行自由組合對外提供服務,例如你有三個原子校驗功能(A:身份證B:銀行卡C:手機號)服務並對外提供呼叫使用。有些呼叫方需要使用AB組合,有些呼叫方需要使用到CBA組合,還有一些可能只使用三者中的一個。那麼這個時候你就可以使用組合模式進行構建服務,對於不同型別的呼叫方配置不同的組織關係樹,而這個樹結構你可以配置到資料庫中也可以不斷的通過圖形介面來控制樹結構。

所以不同的設計模式用在恰當好處的場景可以讓程式碼邏輯非常清晰並易於擴充套件,同時也可以減少團隊新增人員對專案的學習成本。

四、案例場景模擬

場景模式;營銷決策樹

以上是一個非常簡化版的營銷規則決策樹,根據性別年齡來發放不同型別的優惠券,來刺激消費起到精準使用者促活的目的。

雖然一部分小夥伴可能並沒有開發過營銷場景,但你可能時時刻刻的被營銷著。比如你去經常瀏覽男性喜歡的機械鍵盤、膝上型電腦、汽車裝飾等等,那麼久給你推薦此類的優惠券刺激你消費。那麼如果你購物不多,或者錢不在自己手裡。那麼你是否打過車,有一段時間經常有小夥伴喊,為什麼同樣的距離他就10元,我就15元呢?其實這些都是被營銷的案例,一般對於不常使用軟體的小夥伴,經常會進行稍微大力度的促活,增加使用者粘性。

那麼在這裡我們就模擬一個類似的決策場景,體現出組合模式在其中起到的重要性。另外,組合模式不只是可以運用於規則決策樹,還可以做服務包裝將不同的介面進行組合配置,對外提供服務能力,減少開發成本。

五、用一坨坨程式碼實現

這裡我們舉一個關於ifelse誕生的例子,介紹小姐姐與程式設計師?‍?‍之間的故事導致的事故

日期 需求 緊急程度 程式設計師(話外音)
星期一.早上 猿哥哥,老闆說要搞一下營銷拉拉量,給男生女生髮不同的優惠券,促活消費。 很緊急,下班就要 行吧,也不難,加下判斷就上線
星期二.下午 小哥哥,我們們上線後非常好。要讓我們們按照年輕、中年、成年,不同年齡加下判斷,準確刺激消費。 超緊急,明天就要 也不難,加就加吧
星期三.晚上 喂,小哥哥!睡了嗎!老闆說我們們這次活動很成功,可以不可以在細分下,把單身、結婚、有娃的都加上不同判斷。這樣更能刺激使用者消費。 賊緊急,最快上線。 已經意識到ifelse越來越多了
星期四.凌晨 哇!小哥哥你們太棒了,上的真快。嘻嘻!有個小請求,需要調整下年齡段,因為現在學生處物件的都比較早,有物件的更容易買某某某東西。要改下值!辛苦辛苦! 老闆,在等著呢! 一大片的值要修改,哎!這麼多ifelse
星期五.半夜 歪歪喂!巴巴,壞了,怎麼發的優惠券不對了,有客訴了,很多女生都來投訴。你快看看。老闆,他... (一頭汗),哎,值粘錯位置了! 終究還是一個人扛下了所有

1. 工程結構

itstack-demo-design-8-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── EngineController.java
  • 公司裡要都是這樣的程式設計師絕對省下不少成本,根本不要搭建微服務,一個工程搞定所有業務!
  • 但千萬不要這麼幹!酒肉穿腸過,佛祖心中留。世人若學我,如同進魔道。

2. 程式碼實現

public class EngineController {

    private Logger logger = LoggerFactory.getLogger(EngineController.class);

    public String process(final String userId, final String userSex, final int userAge) {

        logger.info("ifelse實現方式判斷使用者結果。userId:{} userSex:{} userAge:{}", userId, userSex, userAge);

        if ("man".equals(userSex)) {
            if (userAge < 25) {
                return "果實A";
            }

            if (userAge >= 25) {
                return "果實B";
            }
        }

        if ("woman".equals(userSex)) {
            if (userAge < 25) {
                return "果實C";
            }

            if (userAge >= 25) {
                return "果實D";
            }
        }

        return null;

    }

}
  • 除了我們說的擴充套件性和每次的維護以外,這樣的程式碼實現起來是最快的。而且從樣子來看也很適合新人理解。
  • 但是我勸你別寫,寫這樣程式碼不是被扣績效就是被開除。

3. 測試驗證

3.1 編寫測試類

@Test
public void test_EngineController() {
    EngineController engineController = new EngineController();
    String process = engineController.process("Oli09pLkdjh", "man", 29);
    logger.info("測試結果:{}", process);
}
  • 這裡我們模擬了一個使用者ID,並傳輸性別:man、年齡:29,我們的預期結果是:果實B。實際對應業務就是給頭禿的程式設計師發一張枸杞優惠券

3.2 測試結果

22:10:12.891 [main] INFO  o.i.demo.design.EngineController - ifelse實現方式判斷使用者結果。userId:Oli09pLkdjh userSex:man userAge:29
22:10:12.898 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:果實B

Process finished with exit code 0
  • 從測試結果上看我們的程式執行正常並且符合預期,只不過實現上並不是我們推薦的。接下來我們會採用組合模式來優化這部分程式碼。

六、組合模式重構程式碼

接下來使用組合模式來進行程式碼優化,也算是一次很小的重構。

接下來的重構部分程式碼改動量相對來說會比較大一些,為了讓我們可以把不同型別的決策節點和最終的果實組裝成一棵可被執行的決策樹,需要做適配設計和工廠方法呼叫,具體會體現在定義介面以及抽象類和初始化配置決策節點(性別年齡)上。建議這部分程式碼多閱讀幾次,最好實踐下。

1. 工程結構

itstack-demo-design-8-02
└── src
    ├── main
    │   └── java
    │      └── org.itstack.demo.design.domain
    │          ├── model
    │          │   ├── aggregates
    │          │   │   └── TreeRich.java
    │          │   └── vo
    │          │       ├── EngineResult.java
    │          │       ├── TreeNode.java
    │          │       ├── TreeNodeLink.java    
    │          │       └── TreeRoot.java    
    │          └── service
    │              ├── engine
    │              │   ├── impl    
    │              │   │   └── TreeEngineHandle.java       
    │              │   ├── EngineBase.java 
    │              │   ├── EngineConfig.java       
    │              │   └── IEngine.java    
    │              └── logic
    │                  ├── impl    
    │                  │   ├── LogicFilter.java     
    │                  │   └── LogicFilter.java        
    │                  └── LogicFilter.java    
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java

組合模式模型結構

組合模式模型結構

  • 首先可以看下黑色框框的模擬指導樹結構;11112111112121122,這是一組樹結構的ID,並由節點串聯組合出一棵關係樹樹。
  • 接下來是類圖部分,左側是從LogicFilter開始定義適配的決策過濾器,BaseLogic是對介面的實現,提供最基本的通用方法。UserAgeFilterUserGenerFilter,是兩個具體的實現類用於判斷年齡性別
  • 最後則是對這顆可以被組織出來的決策樹,進行執行的引擎。同樣定義了引擎介面和基礎的配置,在配置裡面設定了需要的模式決策節點。

    • static {
           logicFilterMap = new ConcurrentHashMap<>();
           logicFilterMap.put("userAge", new UserAgeFilter());
           logicFilterMap.put("userGender", new UserGenderFilter());
      }
  • 接下來會對每一個類進行細緻的講解,如果感覺沒有讀懂一定是我作者的表述不夠清晰,可以新增我的微信(fustack)與我交流。

2. 程式碼實現

2.1 基礎物件

包路徑 介紹
model.aggregates TreeRich 聚合物件,包含組織樹資訊
model.vo EngineResult 決策返回物件資訊
model.vo TreeNode 樹節點;子葉節點、果實節點
model.vo TreeNodeLink 樹節點連結鏈路
model.vo TreeRoot 樹根資訊
  • 以上這部分簡單介紹,不包含邏輯只是各項必要屬性的get/set,整個原始碼可以通過關注微信公眾號:bugstack蟲洞棧,回覆原始碼下載開啟連結獲取。

2.2 樹節點邏輯過濾器介面

public interface LogicFilter {

    /**
     * 邏輯決策器
     *
     * @param matterValue          決策值
     * @param treeNodeLineInfoList 決策節點
     * @return 下一個節點Id
     */
    Long filter(String matterValue, List<TreeNodeLink> treeNodeLineInfoList);

    /**
     * 獲取決策值
     *
     * @param decisionMatter 決策物料
     * @return 決策值
     */
    String matterValue(Long treeId, String userId, Map<String, String> decisionMatter);

}
  • 這一部分定義了適配的通用介面,邏輯決策器、獲取決策值,讓每一個提供決策能力的節點都必須實現此介面,保證統一性。

2.3 決策抽象類提供基礎服務

public abstract class BaseLogic implements LogicFilter {

    @Override
    public Long filter(String matterValue, List<TreeNodeLink> treeNodeLinkList) {
        for (TreeNodeLink nodeLine : treeNodeLinkList) {
            if (decisionLogic(matterValue, nodeLine)) return nodeLine.getNodeIdTo();
        }
        return 0L;
    }

    @Override
    public abstract String matterValue(Long treeId, String userId, Map<String, String> decisionMatter);

    private boolean decisionLogic(String matterValue, TreeNodeLink nodeLink) {
        switch (nodeLink.getRuleLimitType()) {
            case 1:
                return matterValue.equals(nodeLink.getRuleLimitValue());
            case 2:
                return Double.parseDouble(matterValue) > Double.parseDouble(nodeLink.getRuleLimitValue());
            case 3:
                return Double.parseDouble(matterValue) < Double.parseDouble(nodeLink.getRuleLimitValue());
            case 4:
                return Double.parseDouble(matterValue) <= Double.parseDouble(nodeLink.getRuleLimitValue());
            case 5:
                return Double.parseDouble(matterValue) >= Double.parseDouble(nodeLink.getRuleLimitValue());
            default:
                return false;
        }
    }

}
  • 在抽象方法中實現了介面方法,同時定義了基本的決策方法;1、2、3、4、5等於、小於、大於、小於等於、大於等於的判斷邏輯。
  • 同時定義了抽象方法,讓每一個實現介面的類都必須按照規則提供決策值,這個決策值用於做邏輯比對。

2.4 樹節點邏輯實現類

年齡節點

public class UserAgeFilter extends BaseLogic {

    @Override
    public String matterValue(Long treeId, String userId, Map<String, String> decisionMatter) {
        return decisionMatter.get("age");
    }

}

性別節點

public class UserGenderFilter extends BaseLogic {

    @Override
    public String matterValue(Long treeId, String userId, Map<String, String> decisionMatter) {
        return decisionMatter.get("gender");
    }

}
  • 以上兩個決策邏輯的節點獲取值的方式都非常簡單,只是獲取使用者的入參即可。實際的業務開發可以從資料庫、RPC介面、快取運算等各種方式獲取。

2.5 決策引擎介面定義

public interface IEngine {

    EngineResult process(final Long treeId, final String userId, TreeRich treeRich, final Map<String, String> decisionMatter);

}
  • 對於使用方來說也同樣需要定義統一的介面操作,這樣的好處非常方便後續擴充出不同型別的決策引擎,也就是可以建造不同的決策工廠。

2.6 決策節點配置

public class EngineConfig {

    static Map<String, LogicFilter> logicFilterMap;

    static {
        logicFilterMap = new ConcurrentHashMap<>();
        logicFilterMap.put("userAge", new UserAgeFilter());
        logicFilterMap.put("userGender", new UserGenderFilter());
    }

    public Map<String, LogicFilter> getLogicFilterMap() {
        return logicFilterMap;
    }

    public void setLogicFilterMap(Map<String, LogicFilter> logicFilterMap) {
        this.logicFilterMap = logicFilterMap;
    }

}
  • 在這裡將可提供服務的決策節點配置到map結構中,對於這樣的map結構可以抽取到資料庫中,那麼就可以非常方便的管理。

2.7 基礎決策引擎功能

public abstract class EngineBase extends EngineConfig implements IEngine {

    private Logger logger = LoggerFactory.getLogger(EngineBase.class);

    @Override
    public abstract EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String, String> decisionMatter);

    protected TreeNode engineDecisionMaker(TreeRich treeRich, Long treeId, String userId, Map<String, String> decisionMatter) {
        TreeRoot treeRoot = treeRich.getTreeRoot();
        Map<Long, TreeNode> treeNodeMap = treeRich.getTreeNodeMap();
        // 規則樹根ID
        Long rootNodeId = treeRoot.getTreeRootNodeId();
        TreeNode treeNodeInfo = treeNodeMap.get(rootNodeId);
        //節點型別[NodeType];1子葉、2果實
        while (treeNodeInfo.getNodeType().equals(1)) {
            String ruleKey = treeNodeInfo.getRuleKey();
            LogicFilter logicFilter = logicFilterMap.get(ruleKey);
            String matterValue = logicFilter.matterValue(treeId, userId, decisionMatter);
            Long nextNode = logicFilter.filter(matterValue, treeNodeInfo.getTreeNodeLinkList());
            treeNodeInfo = treeNodeMap.get(nextNode);
            logger.info("決策樹引擎=>{} userId:{} treeId:{} treeNode:{} ruleKey:{} matterValue:{}", treeRoot.getTreeName(), userId, treeId, treeNodeInfo.getTreeNodeId(), ruleKey, matterValue);
        }
        return treeNodeInfo;
    }

}
  • 這裡主要提供決策樹流程的處理過程,有點像通過鏈路的關係(性別年齡)在二叉樹中尋找果實節點的過程。
  • 同時提供一個抽象方法,執行決策流程的方法供外部去做具體的實現。

2.8 決策引擎的實現

public class TreeEngineHandle extends EngineBase {

    @Override
    public EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String, String> decisionMatter) {
        // 決策流程
        TreeNode treeNode = engineDecisionMaker(treeRich, treeId, userId, decisionMatter);
        // 決策結果
        return new EngineResult(userId, treeId, treeNode.getTreeNodeId(), treeNode.getNodeValue());
    }

}
  • 這裡對於決策引擎的實現就非常簡單了,通過傳遞進來的必要資訊;決策樹資訊、決策物料值,來做具體的樹形結構決策。

3. 測試驗證

3.1 組裝樹關係

@Before
public void init() {
    // 節點:1
    TreeNode treeNode_01 = new TreeNode();
    treeNode_01.setTreeId(10001L);
    treeNode_01.setTreeNodeId(1L);
    treeNode_01.setNodeType(1);
    treeNode_01.setNodeValue(null);
    treeNode_01.setRuleKey("userGender");
    treeNode_01.setRuleDesc("使用者性別[男/女]");
    // 連結:1->11
    TreeNodeLink treeNodeLink_11 = new TreeNodeLink();
    treeNodeLink_11.setNodeIdFrom(1L);
    treeNodeLink_11.setNodeIdTo(11L);
    treeNodeLink_11.setRuleLimitType(1);
    treeNodeLink_11.setRuleLimitValue("man");
    // 連結:1->12
    TreeNodeLink treeNodeLink_12 = new TreeNodeLink();
    treeNodeLink_12.setNodeIdTo(1L);
    treeNodeLink_12.setNodeIdTo(12L);
    treeNodeLink_12.setRuleLimitType(1);
    treeNodeLink_12.setRuleLimitValue("woman");
    List<TreeNodeLink> treeNodeLinkList_1 = new ArrayList<>();
    treeNodeLinkList_1.add(treeNodeLink_11);
    treeNodeLinkList_1.add(treeNodeLink_12);
    treeNode_01.setTreeNodeLinkList(treeNodeLinkList_1);
    // 節點:11
    TreeNode treeNode_11 = new TreeNode();
    treeNode_11.setTreeId(10001L);
    treeNode_11.setTreeNodeId(11L);
    treeNode_11.setNodeType(1);
    treeNode_11.setNodeValue(null);
    treeNode_11.setRuleKey("userAge");
    treeNode_11.setRuleDesc("使用者年齡");
    // 連結:11->111
    TreeNodeLink treeNodeLink_111 = new TreeNodeLink();
    treeNodeLink_111.setNodeIdFrom(11L);
    treeNodeLink_111.setNodeIdTo(111L);
    treeNodeLink_111.setRuleLimitType(3);
    treeNodeLink_111.setRuleLimitValue("25");
    // 連結:11->112
    TreeNodeLink treeNodeLink_112 = new TreeNodeLink();
    treeNodeLink_112.setNodeIdFrom(11L);
    treeNodeLink_112.setNodeIdTo(112L);
    treeNodeLink_112.setRuleLimitType(5);
    treeNodeLink_112.setRuleLimitValue("25");
    List<TreeNodeLink> treeNodeLinkList_11 = new ArrayList<>();
    treeNodeLinkList_11.add(treeNodeLink_111);
    treeNodeLinkList_11.add(treeNodeLink_112);
    treeNode_11.setTreeNodeLinkList(treeNodeLinkList_11);
    // 節點:12
    TreeNode treeNode_12 = new TreeNode();
    treeNode_12.setTreeId(10001L);
    treeNode_12.setTreeNodeId(12L);
    treeNode_12.setNodeType(1);
    treeNode_12.setNodeValue(null);
    treeNode_12.setRuleKey("userAge");
    treeNode_12.setRuleDesc("使用者年齡");
    // 連結:12->121
    TreeNodeLink treeNodeLink_121 = new TreeNodeLink();
    treeNodeLink_121.setNodeIdFrom(12L);
    treeNodeLink_121.setNodeIdTo(121L);
    treeNodeLink_121.setRuleLimitType(3);
    treeNodeLink_121.setRuleLimitValue("25");
    // 連結:12->122
    TreeNodeLink treeNodeLink_122 = new TreeNodeLink();
    treeNodeLink_122.setNodeIdFrom(12L);
    treeNodeLink_122.setNodeIdTo(122L);
    treeNodeLink_122.setRuleLimitType(5);
    treeNodeLink_122.setRuleLimitValue("25");
    List<TreeNodeLink> treeNodeLinkList_12 = new ArrayList<>();
    treeNodeLinkList_12.add(treeNodeLink_121);
    treeNodeLinkList_12.add(treeNodeLink_122);
    treeNode_12.setTreeNodeLinkList(treeNodeLinkList_12);
    // 節點:111
    TreeNode treeNode_111 = new TreeNode();
    treeNode_111.setTreeId(10001L);
    treeNode_111.setTreeNodeId(111L);
    treeNode_111.setNodeType(2);
    treeNode_111.setNodeValue("果實A");
    // 節點:112
    TreeNode treeNode_112 = new TreeNode();
    treeNode_112.setTreeId(10001L);
    treeNode_112.setTreeNodeId(112L);
    treeNode_112.setNodeType(2);
    treeNode_112.setNodeValue("果實B");
    // 節點:121
    TreeNode treeNode_121 = new TreeNode();
    treeNode_121.setTreeId(10001L);
    treeNode_121.setTreeNodeId(121L);
    treeNode_121.setNodeType(2);
    treeNode_121.setNodeValue("果實C");
    // 節點:122
    TreeNode treeNode_122 = new TreeNode();
    treeNode_122.setTreeId(10001L);
    treeNode_122.setTreeNodeId(122L);
    treeNode_122.setNodeType(2);
    treeNode_122.setNodeValue("果實D");
    // 樹根
    TreeRoot treeRoot = new TreeRoot();
    treeRoot.setTreeId(10001L);
    treeRoot.setTreeRootNodeId(1L);
    treeRoot.setTreeName("規則決策樹");
    Map<Long, TreeNode> treeNodeMap = new HashMap<>();
    treeNodeMap.put(1L, treeNode_01);
    treeNodeMap.put(11L, treeNode_11);
    treeNodeMap.put(12L, treeNode_12);
    treeNodeMap.put(111L, treeNode_111);
    treeNodeMap.put(112L, treeNode_112);
    treeNodeMap.put(121L, treeNode_121);
    treeNodeMap.put(122L, treeNode_122);
    treeRich = new TreeRich(treeRoot, treeNodeMap);
}

樹形結構的組織關係

  • 重要,這一部分是組合模式非常重要的使用,在我們已經建造好的決策樹關係下,可以建立出樹的各個節點,以及對節點間使用鏈路進行串聯。
  • 及時後續你需要做任何業務的擴充套件都可以在裡面新增相應的節點,並做動態化的配置。
  • 關於這部分手動組合的方式可以提取到資料庫中,那麼也就可以擴充套件到圖形介面的進行配置操作。

3.2 編寫測試類

@Test
public void test_tree() {
    logger.info("決策樹組合結構資訊:\r\n" + JSON.toJSONString(treeRich));
    
    IEngine treeEngineHandle = new TreeEngineHandle();
    Map<String, String> decisionMatter = new HashMap<>();
    decisionMatter.put("gender", "man");
    decisionMatter.put("age", "29");
    
    EngineResult result = treeEngineHandle.process(10001L, "Oli09pLkdjh", treeRich, decisionMatter);
    
    logger.info("測試結果:{}", JSON.toJSONString(result));
}
  • 在這裡提供了呼叫的通過組織模式建立出來的流程決策樹,呼叫的時候傳入了決策樹的ID,那麼如果是業務開發中就可以方便的解耦決策樹與業務的繫結關係,按需傳入決策樹ID即可。
  • 此外入參我們還提供了需要處理;(man)、年齡(29歲),的引數資訊。

3.3 測試結果

23:35:05.711 [main] INFO  o.i.d.d.d.service.engine.EngineBase - 決策樹引擎=>規則決策樹 userId:Oli09pLkdjh treeId:10001 treeNode:11 ruleKey:userGender matterValue:man
23:35:05.712 [main] INFO  o.i.d.d.d.service.engine.EngineBase - 決策樹引擎=>規則決策樹 userId:Oli09pLkdjh treeId:10001 treeNode:112 ruleKey:userAge matterValue:29
23:35:05.715 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:{"nodeId":112,"nodeValue":"果實B","success":true,"treeId":10001,"userId":"Oli09pLkdjh"}

Process finished with exit code 0
  • 從測試結果上看這與我們使用ifelse是一樣的,但是目前這與的組合模式設計下,就非常方便後續的擴充和修改。
  • 整體的組織關係框架以及呼叫決策流程已經搭建完成,如果閱讀到此沒有完全理解,可以下載程式碼觀察結構並執行除錯。

七、總結

  • 從以上的決策樹場景來看,組合模式的主要解決的是一系列簡單邏輯節點或者擴充套件的複雜邏輯節點在不同結構的組織下,對於外部的呼叫是仍然可以非常簡單的。
  • 這部分設計模式保證了開閉原則,無需更改模型結構你就可以提供新的邏輯節點的使用並配合組織出新的關係樹。但如果是一些功能差異化非常大的介面進行包裝就會變得比較困難,但也不是不能很好的處理,只不過需要做一些適配和特定化的開發。
  • 很多時候因為你的極致追求和稍有倔強的工匠精神,即使在面對同樣的業務需求,你能完成出最好的程式碼結構和最易於擴充套件的技術架構。不要被遠不能給你指導提升能力的影響到放棄自己的追求!

八、推薦閱讀

相關文章