設計模式--組合模式--商品排序案例

所有的例子均來源與實際開發項目
本節介紹組合模式的使用–商品結果排序評分系統

首先還是重複一下:設計模式是思路,而不是一味套用,如果業務場景和功能需求恰好吻合,那最好不過;如果有偏差,一定要具體情況具體分析,更具實際場景選擇合適的模式類型(注意,是類型,並不特定指某種模式,有的時候一個場景多種模式都可以做)


本節所舉得例子爲商品結果排序評分系統,也就是很多項目中,在比較重要任何事物查詢完畢後,會有一個排序過程,比如在淘寶上搜索完商品後那個商品列表的排序過程。而且其複雜度當然遠遠超過數據中SQL語句可以完成的程度(當然不排除有公司把邏輯直接寫入到存儲過程中,但是這種情況不做探討),所以需要一個完整的評分系統來對結果進行排序,高分排前,低分置後。

 

Ⅰ 【分析階段】———————————

a.首先分析業務功能(功能需求):類似於上面所述的,就是對結果進行排序,高分前,低分後。再深入到實現代碼層面,就是多層次的權重分數計算了,這個類似大家學校裏,最終期末總評分,30%平時分,70%考試分一個道理;

b.然後分析擴展性(非功能需求):主要變化會在哪?當然毫無疑問就是:

  • 評分的權重會發生變化:之前佔80%的比例,現在被削弱了,只佔20%了;
  • 評分的相互層次關係可能變化:之前這個評分是最低一級的了,現在在它下面又多了兩級更細化的評分;
  • 乃至評分的邏輯處理會發生變化…

好了,結合上面兩點,你會發現,這有點一個“樹”的意思了,不是麼?請看下面這個圖


左邊一個一般的樹圖,類似資源管理器的列表單,這個摘自《研磨設計模式》P392頁的組合模式章節配圖

右邊是我剛剛所說的業務功能的分析圖,一個商品的綜合總評分分爲“商品評分”與發佈該商品的“店鋪評分”,分別佔總評分的80%與20%,然後其下面又分別有子評分標準,以此類推,圖片應該不難理解

這個時候,當然需要你對組合模式還是有了瞭解,至少要有印象,知道有這麼一個模式可以作爲備選,然後一比較,發現確實也湊巧,這個例子非常符合組合模式,而且看起來至少目前不需要做太大改動,已經可以滿足需求了。那就開始設計吧!


Ⅱ【設計階段】——————————————————-

類圖設計如下,以下圖片中上半部分摘自《研磨設計模式》P396 圖15.1 ,下面爲我基於他的圖進行的部分修改,即爲我的組合模式的設計


是不是發現還是變化了不少,這就是我之前在第一篇文章設計模式–概述【請看清本質】中所提到的觀點,不用在乎一些改變,而要關注設計模式的核心目標是否達到

好了,繼續對上面的設計做一些說明:

身份對應:

  • ScoreCalculator —-對應—> Component,只是在原來書中例子,component爲接口,我這裏進一步改爲了一個抽象類,因爲其中還嵌入了一個模板模式的做法,我在代碼中會有說明;
  • XXScoreCalculator — 對應 —> Composite , 其實就是具體的實現類了
  • Leaf —- > ?? 在這裏,我刪去了Leaf這個角色,爲什麼?因爲在書中例子裏,Leaf作爲葉子節點,即樹結構的最末端節點是不會有“子節點”存在的,所以設置了這一角色,但是我這裏由於各種計算分數的規則在將來隨時可能由於業務的變更會要產生子節點,這樣就不可能把任何一個分數計算規則指定爲“無子節點”的類型,所以我索性去掉了Leaf角色

具體實現,我結合代碼示例進行說明:

/**
* 評分計算基類
* 這裏我略掉了一些什麼addChild,removeChild等方法的說明,這個大家需要自行加入
*/
public abstract class ScoreCalculator {

    // list用於存放子類
    // 至於我這裏爲什麼還封裝到了一個Entry裏,只是完全出於業務考慮,因爲既要放規則計算類,又要放入配置文件導入的權重參數
    // 像這個就需要大家根據自己業務靈活處理
    protected List scoreEntryList;

    public int calculateScore(){

        int totalScore = 0;

        //先進行子節點的分數計算,對葉子節點來說,由於沒有子節點,這個代碼段就自然不會執行了
        if(this.scoreEntryList!=null){
            int[] scoreArray = new int[scoreEntryList.size()];
            ScoreEntry entry = null;
            for (int i =0; i < scoreEntryList.size(); i++) {
                entry = scoreEntryList.get(i);
                //這裏調用子節點的calculateScore(),遞歸!,也就是組合模式的核心思想之一
                scoreArray[i] = (int)(Math.round(entry.cal.calculateScore() * entry.weight));
                totalScore += scoreArray[i];
            }
        }

        //注意,這裏就用了模板方法的思想,上面所做的一切都是不變的,就是計算子節點的分數然後彙總
        //那統計完子節點的了,自己還要進一步做處理吧?畢竟有些簡單的加減乘除就解決了,有些可還有複雜的邏輯
        totalScore = moreCalculate(totalScore);
        return totalScore;
    }

    //抽象方法,讓子類自己去實現
    protected abstract int moreCalculate(int currentTotalScore);

}

public class ScoreEntry {
    //規則計算類
    public ScoreCalculator cal;
    //本規則在上一級規則中所佔的比重(0~1)
    public float weight;
    //本規則在計算後的結果,整型(0~10000)
    public int score;
}

對於具體的實現類來說,就比較清晰了,實現該實現的方法就好了

/*
* 店鋪分數計算規則,其還帶有子類規則A1,A2
* 至於商品分數計算規則的思路就差不多了,這裏就只舉此一個
*/
public class ShopCalculator extends ScoreCalculator {

    //..構造方法等等略

    @Override
    protected int moreCalculate(int currentTotalScore) {

        //進行具體的操作,比如這些分數還要進一步結合店鋪的某種其他信息做檢查處理
        //然後做相應的增減,最後返回數據至上一層,向上一層統計分數節點“呈遞”自己的最終結果
        currentTotalScore = doSth();

        return currentTotalScore;
    }
}

不知道大家是否能夠順利理解,畢竟這個例子是實際項目,邏輯相對複雜,篇幅問題我也只能選擇核心代碼給出,所以難免會看着有點困難,沒關係,重點看我有註釋的那幾行便可,若有問題可以回帖發問~

好了,繼續還有一點收尾,那既然這棵樹“種好了”,那怎麼來使用呢?設計模式不僅僅要便於擴展,也要便於使用,那我們就來看看如何搭建起這個樹結構,也就是上面畫圖中,那個Client怎麼調用了

//首先提醒一句,大家在看上面第二幅圖時,《研磨設計模式》書中圖裏有addChild方法,怎麼樣?想到了怎麼建立樹了嗎?
//對吧,其實很簡單,new出節點,然後通過addChild建立起雙方的父子關係便可了

//總分統計
ScoreCalculator totalCal = new TotalCalculator();
//商品統計,數字是在上級中的所佔權重
ScoreCalculator goodsCal = new GoodsCalculator(0.8);
//店鋪統計
ScoreCalculator shopCal = new ShopCalculator(0.2);
//A1統計
ScoreCalculator a1Cal = new A1Calculator(0.3);
//A2統計
ScoreCalculator a2Cal = new A2Calculator(0.7);
...(略)

//好了,把相互的關係整理起來!
goodsCal.addChild(a1Cal);
goodsCal.addChild(a2Cal);
...
totalCal.addChild(goodsCal);
totalCal.addChild(shopCal);

//最後,執行計算就OK了!無窮的遞歸就在這一刻開始
int finalScore = totalCal.calculateScore();

Ⅲ【覈查階段】———————————————————–

好了,到這裏爲止,這個樣例算是分析完了,只是還查了一點,什麼呢?檢查!
就是檢查這個最終的開發結果是不是達到的之前的設計目的:

  • 業務功能是否滿足?當然滿足了,是按照權重計算,分層遞歸網上走,層層按權重統計彙總,最終出總結果
  • 擴展性是否良好?沒問題,當有新的規則出現時,創建一個新的繼承自ScoreCalculator的類,然後實現moreCalculator方法進行自身邏輯處理即可;
  • 能否處理業務變化?首先,每個規則之間業務處理完全分開,權重修改加個個配置文件即可搞定,變更修改不會對其他業務構成影響;然後當有節點層次關係發生變化時,在Client調用中進行變更child層次結構即可

可見,目的都算是達到了,這個模式的使用就算是合格了!

在這裏,大家發現我其中用上了模板模式,其實在這個以組合模式爲核心的模塊裏裏外外還包圍着很多其他模式來進行輔助,這裏我爲了避免影響都沒有寫出來了,有點像變形金剛合體一樣。這依舊說明了一點:

模式爲思想,方法不固定,靈活變通使用纔是硬道理!



歡迎訪問我的主頁,最新的文章我會首先發布在個人主頁上:

http://blog.guaidm.com/shocky





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