全部的样例均来源与实际开发项目
本节介绍组合模式的使用–商品结果排序评分系统
首先还是反复一下:设计模式是思路。而不是一味套用,假设业务场景和功能需求恰好吻合。那最好只是。假设有偏差。一定要详细情况详细分析,更具实际场景选择合适的模式类型(注意。是类型,并不特定指某种模式,有的时候一个场景多种模式都能够做)
本节所举得样例为商品结果排序评分系统,也就是非常多项目中,在比較重要不论什么事物查询完毕后,会有一个排序过程。比方在淘宝上搜索完商品后那个商品列表的排序过程。
并且其复杂度当然远远超过数据中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方法。怎么样?想到了怎么建立树了吗? //对吧,事实上非常easy。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层次结构就可以
可见。目的都算是达到了,这个模式的使用就算是合格了!
在这里,大家发现我当中用上了模板模式,事实上在这个以组合模式为核心的模块里里外外还包围着非常多其它模式来进行辅助。这里我为了避免影响都没有写出来了,有点像变形金刚合体一样。
这依然说明了一点:
模式为思想。方法不固定。灵活变通使用才是硬道理。
欢迎訪问我的主页。最新的文章我会首先公布在个人主页上: