當(dāng)你腦海里出現(xiàn)一棵樹(shù)的時(shí)候
所有的例子均來(lái)源與實(shí)際開(kāi)發(fā)項(xiàng)目
本節(jié)介紹組合模式的使用–商品結(jié)果排序評(píng)分系統(tǒng)
首先還是重復(fù)一下:設(shè)計(jì)模式是思路,而不是一味套用,如果業(yè)務(wù)場(chǎng)景和功能需求恰好吻合,那最好不過(guò)苗分;如果有偏差,一定要具體情況具體分析遍烦,更具實(shí)際場(chǎng)景選擇合適的模式類(lèi)型(注意俭嘁,是類(lèi)型,并不特定指某種模式服猪,有的時(shí)候一個(gè)場(chǎng)景多種模式都可以做)
本節(jié)所舉得例子為商品結(jié)果排序評(píng)分系統(tǒng),也就是很多項(xiàng)目中拐云,在比較重要任何事物查詢完畢后罢猪,會(huì)有一個(gè)排序過(guò)程,比如在淘寶上搜索完商品后那個(gè)商品列表的排序過(guò)程叉瘩。而且其復(fù)雜度當(dāng)然遠(yuǎn)遠(yuǎn)超過(guò)數(shù)據(jù)中SQL語(yǔ)句可以完成的程度(當(dāng)然不排除有公司把邏輯直接寫(xiě)入到存儲(chǔ)過(guò)程中膳帕,但是這種情況不做探討),所以需要一個(gè)完整的評(píng)分系統(tǒng)來(lái)對(duì)結(jié)果進(jìn)行排序薇缅,高分排前危彩,低分置后。
Ⅰ 【分析階段】———————————
a.首先分析業(yè)務(wù)功能(功能需求):類(lèi)似于上面所述的泳桦,就是對(duì)結(jié)果進(jìn)行排序汤徽,高分前,低分后灸撰。再深入到實(shí)現(xiàn)代碼層面谒府,就是多層次的權(quán)重分?jǐn)?shù)計(jì)算了,這個(gè)類(lèi)似大家學(xué)校里浮毯,最終期末總評(píng)分完疫,30%平時(shí)分,70%考試分一個(gè)道理债蓝;
b.然后分析擴(kuò)展性(非功能需求):主要變化會(huì)在哪壳鹤?當(dāng)然毫無(wú)疑問(wèn)就是:
評(píng)分的權(quán)重會(huì)發(fā)生變化:之前占80%的比例价捧,現(xiàn)在被削弱了祷愉,只占20%了与纽;評(píng)分的相互層次關(guān)系可能變化:之前這個(gè)評(píng)分是最低一級(jí)的了浇垦,現(xiàn)在在它下面又多了兩級(jí)更細(xì)化的評(píng)分;乃至評(píng)分的邏輯處理會(huì)發(fā)生變化…
好了兆沙,結(jié)合上面兩點(diǎn)欧芽,你會(huì)發(fā)現(xiàn),這有點(diǎn)一個(gè)“樹(shù)”的意思了葛圃,不是么千扔?請(qǐng)看下面這個(gè)圖
左邊一個(gè)一般的樹(shù)圖,類(lèi)似資源管理器的列表單库正,這個(gè)摘自《研磨設(shè)計(jì)模式》P392頁(yè)的組合模式章節(jié)配圖
右邊是我剛剛所說(shuō)的業(yè)務(wù)功能的分析圖曲楚,一個(gè)商品的綜合總評(píng)分分為“商品評(píng)分”與發(fā)布該商品的“店鋪評(píng)分”,分別占總評(píng)分的80%與20%褥符,然后其下面又分別有子評(píng)分標(biāo)準(zhǔn)龙誊,以此類(lèi)推,圖片應(yīng)該不難理解
這個(gè)時(shí)候喷楣,當(dāng)然需要你對(duì)組合模式還是有了了解趟大,至少要有印象,知道有這么一個(gè)模式可以作為備選铣焊,然后一比較逊朽,發(fā)現(xiàn)確實(shí)也湊巧,這個(gè)例子非常符合組合模式曲伊,而且看起來(lái)至少目前不需要做太大改動(dòng)叽讳,已經(jīng)可以滿足需求了。那就開(kāi)始設(shè)計(jì)吧坟募!
Ⅱ【設(shè)計(jì)階段】——————————————————-
類(lèi)圖設(shè)計(jì)如下岛蚤,以下圖片中上半部分摘自《研磨設(shè)計(jì)模式》P396 圖15.1 ,下面為我基于他的圖進(jìn)行的部分修改懈糯,即為我的組合模式的設(shè)計(jì)
是不是發(fā)現(xiàn)還是變化了不少涤妒,這就是我之前在第一篇文章設(shè)計(jì)模式–概述,看清本質(zhì)中所提到的觀點(diǎn)昂利,不用在乎一些改變届腐,而要關(guān)注設(shè)計(jì)模式的核心目標(biāo)是否達(dá)到
好了,繼續(xù)對(duì)上面的設(shè)計(jì)做一些說(shuō)明:
身份對(duì)應(yīng):
ScoreCalculator —-對(duì)應(yīng)—> Component蜂奸,只是在原來(lái)書(shū)中例子犁苏,component為接口,我這里進(jìn)一步改為了一個(gè)抽象類(lèi)扩所,因?yàn)槠渲羞€嵌入了一個(gè)模板模式的做法围详,我在代碼中會(huì)有說(shuō)明;
XXScoreCalculator — 對(duì)應(yīng) —> Composite , 其實(shí)就是具體的實(shí)現(xiàn)類(lèi)了
Leaf —- > ?? 在這里助赞,我刪去了Leaf這個(gè)角色买羞,為什么?因?yàn)樵跁?shū)中例子里雹食,Leaf作為葉子節(jié)點(diǎn)畜普,即樹(shù)結(jié)構(gòu)的最末端節(jié)點(diǎn)是不會(huì)有“子節(jié)點(diǎn)”存在的,所以設(shè)置了這一角色群叶,但是我這里由于各種計(jì)算分?jǐn)?shù)的規(guī)則在將來(lái)隨時(shí)可能由于業(yè)務(wù)的變更會(huì)要產(chǎn)生子節(jié)點(diǎn)吃挑,這樣就不可能把任何一個(gè)分?jǐn)?shù)計(jì)算規(guī)則指定為“無(wú)子節(jié)點(diǎn)”的類(lèi)型,所以我索性去掉了Leaf角色
具體實(shí)現(xiàn)街立,我結(jié)合代碼示例進(jìn)行說(shuō)明:
/**
* 評(píng)分計(jì)算基類(lèi)
* 這里我略掉了一些什么addChild,removeChild等方法的說(shuō)明舶衬,這個(gè)大家需要自行加入
*/
public abstract class ScoreCalculator {
// list用于存放子類(lèi)
// 至于我這里為什么還封裝到了一個(gè)Entry里,只是完全出于業(yè)務(wù)考慮赎离,因?yàn)榧纫乓?guī)則計(jì)算類(lèi)逛犹,又要放入配置文件導(dǎo)入的權(quán)重參數(shù)
// 像這個(gè)就需要大家根據(jù)自己業(yè)務(wù)靈活處理
protected List scoreEntryList;
public int calculateScore(){
int totalScore = 0;
//先進(jìn)行子節(jié)點(diǎn)的分?jǐn)?shù)計(jì)算,對(duì)葉子節(jié)點(diǎn)來(lái)說(shuō)梁剔,由于沒(méi)有子節(jié)點(diǎn)虽画,這個(gè)代碼段就自然不會(huì)執(zhí)行了
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);
//這里調(diào)用子節(jié)點(diǎn)的calculateScore(),遞歸!憾朴,也就是組合模式的核心思想之一
scoreArray[i] = (int)(Math.round(entry.cal.calculateScore() * entry.weight));
totalScore += scoreArray[i];
}
}
//注意狸捕,這里就用了模板方法的思想,上面所做的一切都是不變的众雷,就是計(jì)算子節(jié)點(diǎn)的分?jǐn)?shù)然后匯總
//那統(tǒng)計(jì)完子節(jié)點(diǎn)的了,自己還要進(jìn)一步做處理吧做祝?畢竟有些簡(jiǎn)單的加減乘除就解決了砾省,有些可還有復(fù)雜的邏輯
totalScore = moreCalculate(totalScore);
return totalScore;
}
//抽象方法,讓子類(lèi)自己去實(shí)現(xiàn)
protected abstract int moreCalculate(int currentTotalScore);
}
public class ScoreEntry {
//規(guī)則計(jì)算類(lèi)
public ScoreCalculator cal;
//本規(guī)則在上一級(jí)規(guī)則中所占的比重(0~1)
public float weight;
//本規(guī)則在計(jì)算后的結(jié)果,整型(0~10000)
public int score;
}
對(duì)于具體的實(shí)現(xiàn)類(lèi)來(lái)說(shuō)混槐,就比較清晰了编兄,實(shí)現(xiàn)該實(shí)現(xiàn)的方法就好了
/*
* 店鋪分?jǐn)?shù)計(jì)算規(guī)則,其還帶有子類(lèi)規(guī)則A1,A2
* 至于商品分?jǐn)?shù)計(jì)算規(guī)則的思路就差不多了声登,這里就只舉此一個(gè)
*/
public class ShopCalculator extends ScoreCalculator {
//..構(gòu)造方法等等略
@Override
protected int moreCalculate(int currentTotalScore) {
//進(jìn)行具體的操作狠鸳,比如這些分?jǐn)?shù)還要進(jìn)一步結(jié)合店鋪的某種其他信息做檢查處理
//然后做相應(yīng)的增減,最后返回?cái)?shù)據(jù)至上一層悯嗓,向上一層統(tǒng)計(jì)分?jǐn)?shù)節(jié)點(diǎn)“呈遞”自己的最終結(jié)果
currentTotalScore = doSth();
return currentTotalScore;
}
}
不知道大家是否能夠順利理解件舵,畢竟這個(gè)例子是實(shí)際項(xiàng)目,邏輯相對(duì)復(fù)雜脯厨,篇幅問(wèn)題我也只能選擇核心代碼給出铅祸,所以難免會(huì)看著有點(diǎn)困難,沒(méi)關(guān)系,重點(diǎn)看我有注釋的那幾行便可临梗,若有問(wèn)題可以回帖發(fā)問(wèn)~
好了涡扼,繼續(xù)還有一點(diǎn)收尾,那既然這棵樹(shù)“種好了”盟庞,那怎么來(lái)使用呢吃沪?設(shè)計(jì)模式不僅僅要便于擴(kuò)展,也要便于使用什猖,那我們就來(lái)看看如何搭建起這個(gè)樹(shù)結(jié)構(gòu)票彪,也就是上面畫(huà)圖中,那個(gè)Client怎么調(diào)用了
//提醒卸伞,大家在看上面第二幅圖時(shí)抹镊,《研磨設(shè)計(jì)模式》書(shū)中圖里有addChild方法,怎么樣荤傲?想到了怎么建立樹(shù)了嗎垮耳?
//對(duì)吧,其實(shí)很簡(jiǎn)單遂黍,new出節(jié)點(diǎn)终佛,然后通過(guò)addChild建立起雙方的父子關(guān)系便可了
//總分統(tǒng)計(jì)
ScoreCalculator totalCal = new TotalCalculator();
//商品統(tǒng)計(jì),數(shù)字是在上級(jí)中的所占權(quán)重
ScoreCalculator goodsCal = new GoodsCalculator(0.8);
//店鋪統(tǒng)計(jì)
ScoreCalculator shopCal = new ShopCalculator(0.2);
//A1統(tǒng)計(jì)
ScoreCalculator a1Cal = new A1Calculator(0.3);
//A2統(tǒng)計(jì)
ScoreCalculator a2Cal = new A2Calculator(0.7);
...(略)
//好了雾家,把相互的關(guān)系整理起來(lái)铃彰!
goodsCal.addChild(a1Cal);
goodsCal.addChild(a2Cal);
...
totalCal.addChild(goodsCal);
totalCal.addChild(shopCal);
//最后,執(zhí)行計(jì)算就OK了芯咧!無(wú)窮的遞歸就在這一刻開(kāi)始
int finalScore = totalCal.calculateScore();
Ⅲ【核查階段】———————————————————–
好了牙捉,到這里為止,這個(gè)樣例算是分析完了敬飒,只是還查了一點(diǎn)邪铲,什么呢?檢查无拗!
就是檢查這個(gè)最終的開(kāi)發(fā)結(jié)果是不是達(dá)到的之前的設(shè)計(jì)目的:
a.業(yè)務(wù)功能是否滿足带到? 當(dāng)然滿足了,是按照權(quán)重計(jì)算英染,分層遞歸網(wǎng)上走揽惹,層層按權(quán)重統(tǒng)計(jì)匯總,最終出總結(jié)果
b.擴(kuò)展性是否良好四康? 沒(méi)問(wèn)題搪搏,當(dāng)有新的規(guī)則出現(xiàn)時(shí),創(chuàng)建一個(gè)新的繼承自ScoreCalculator的類(lèi)箭养,然后實(shí)現(xiàn)moreCalculator方法進(jìn)行自身邏輯處理即可慕嚷;
c.能否處理業(yè)務(wù)變化? 首先,每個(gè)規(guī)則之間業(yè)務(wù)處理完全分開(kāi)喝检,權(quán)重修改加個(gè)個(gè)配置文件即可搞定嗅辣,變更修改不會(huì)對(duì)其他業(yè)務(wù)構(gòu)成影響;然后當(dāng)有節(jié)點(diǎn)層次關(guān)系發(fā)生變化時(shí)挠说,在Client調(diào)用中進(jìn)行變更c(diǎn)hild層次結(jié)構(gòu)即可
可見(jiàn)澡谭,目的都算是達(dá)到了,這個(gè)模式的使用就算是合格了损俭!
在這里蛙奖,大家發(fā)現(xiàn)我其中用上了模板模式,其實(shí)在這個(gè)以組合模式為核心的模塊里里外外還包圍著很多其他模式來(lái)進(jìn)行輔助杆兵,這里我為了避免影響都沒(méi)有寫(xiě)出來(lái)了雁仲,有點(diǎn)像變形金剛合體一樣。這依舊說(shuō)明了一點(diǎn):
模式為思想琐脏,方法不固定攒砖,靈活變通使用才是硬道理!