这一课讲装饰器模式。什么在变:功能组合的需求频繁变化,排列组合爆炸。怎么挡:每个功能是一层皮,按需套叠,不搞排列组合。
产品经理李婷拍在少主桌上的不是报告,是一份型号清单。
四十七个。
"基础款加上各种功能组合,已经排出四十七个型号了。上周赵闯又接了个客户,要高端木材加环保认证加进口五金加刻字服务。这是一个新的排列组合。"
少主看着那份清单,上面密密麻麻写满了编号:BF-001(基础款)、BF-001-E(基础+环保)、BF-001-H(基础+进口五金)、BF-001-EH(基础+环保+进口五金)、BF-001-EL(基础+环保+加急)......
以此类推,四十七个。
"这些型号之间有什么区别?"少主问。
"核心产品一样,就是往上叠功能。"李婷翻到最后一页,"但现在每加一个新功能,我要跟所有排列组合做交叉,生成新型号。加一个功能不是加一个,是加一排。"
少主算了一下:基础功能加三个可选项,排列组合是七个型号。加第四个可选项,变成十五个。第五个,三十一个。
"你现在是四十七个,"少主说,"如果赵闯再卖进来两个新需求------"
"就是一百多个。我知道。"李婷的语气说明她已经算过了。
少主没马上答复李婷。他去翻了翻近半年的销售数据。
四十七个型号里,真正有销量的只有十二个。其余三十五个型号要么只卖过一两单,要么根本没卖过。有些型号的差别就是"高端木材加进口五金"和"进口五金加高端木材"------功能完全一样,只是组合编号不同。
更麻烦的是售后。客服部拿着客户的订单来问技术参数,如果型号是 BF-001-EHL,客服得查对应表才知道这个型号包含哪些功能。四十七个型号的对应表,客服已经背不下来了。
少主把李婷叫回来。
"如果我说,基础款就只有一个。每个功能是一层皮,客户要什么往上套。不管套几层,底层都是同一个基础款。你觉得可行吗?"
李婷想了一会儿:"那型号怎么编?"
"不编。基础款有一个编号。每个功能有自己的编号。客户的订单上写:基础款 + 环保认证 + 进口五金 + 刻字。不是 BF-001-EHL。是 BF-001 + E + H + L。"
"客服不用背四十七个型号了。"
"生产也不用备四十七个版本的料了。基础款备一批,每个功能组件备一批。要什么组合,最后一步套上去。"
李婷盯着少主看了一会儿:"那加新功能呢?"
"加新功能就写一个新的功能组件。不用管已有多少个组合,新组件跟基础款对接就行。"
"一百个组合也不用管?"
"不管。因为组合根本不存在。组合是套的时候临时生成的,不是提前定义的。"
少主跟李婷说的那个方案------基础款只有一个,每个功能是一层皮------在程序设计里有一个名字:装饰器模式。
每个功能是同接口的装饰器,按需叠加。想加功能套一层,想减功能去掉那层,不存在排列组合。
wrapped
<<interface>>
Furniture
+getDescription() : string
+getPrice() : float
BaseProduct
+getDescription() : string
+getPrice() : float
<<abstract>>
Decorator
#wrapped: Furniture
+getDescription() : string
+getPrice() : float
EcoCertDecorator
+getDescription() : string
+getPrice() : float
ImportHardwareDecorator
+getDescription() : string
+getPrice() : float
EngraveDecorator
+getDescription() : string
+getPrice() : float
客户最终拿到的产品,其实就是一个俄罗斯套娃:
刻字( 进口五金( 环保认证( 基础款BF-001 ) ) )。最里面那层始终是同一个东西。
孙子说:"水因地而制流,兵因敌而制胜。故兵无常势,水无常形。"装饰器做的就是这种"无常形"------基础款是水源,功能叠加是因地制宜的流向。不需要穷举所有的河道,遇到了什么需求,就顺势加上哪一层包装。
本文所有人物、情节、公司名均为虚构,如有雷同,纯属巧合。