今天要来介绍一个比较特别、平常可能不太常见的模式。就让我们直接进入问题吧
假设有间百货公司周年庆,为了回馈会员,决定发送福袋给大家。在福袋当中,会放入价值不等的奖品,不过在准备福袋的时候,为了增加惊喜感,老板改变规则,决定让福袋当中可以放入各种不同的福袋。所以有可能会员收到福袋的时候,会像收到俄罗斯娃娃一样,可以一层一层打开福袋,然後看到不同的奖品。
但是这时候经理就头痛了,为了要掌握预算,本来只要清点每个福袋的奖品总额就行,但是当福袋当中还有福袋的时候,要计算奖品总额就变得相对困难了,这时候该怎麽办呢?
这时候一位工程师就说话了:不如我们让福袋自己可以回报奖品总额吧!但是该怎麽做呢?
首先,我们先建立一个抽象类别,他定义了两个基本的方法:add
和 getPrize
。同样的在抽象类别当中,我们没有定义它们的实作细节
abstract class Gift {
add(gift: Gift): void {}
abstract getPrize(): number
}
接着,我们建立奖品类别 Prize
,他继承了 Gift
类别,并定义 getPrize
的实作细节。另一方面,也建立了 prize
属性以及 constructor
class Prize extends Gift {
private prize: number
constructor(prize: number) {
super()
this.prize = prize
}
getPrize(): number {
return this.prize
}
}
最後,来建立福袋类别 Package
,这里同样继承了Gift
类别,并定义跟 Prize
不太一样的 getPrize
实作细节,以及一个不同的属性 children
class Package extends Gift {
private children: Gift[] = []
add(gift: Gift): void {
this.children.push(gift)
}
getPrize(): number {
return this.children.reduce((acc, item) => acc += item.getPrize(), 0)
}
}
接下来我们可以做什麽呢?我们可以建立福袋 package1,以及奖品 prizeA 和 prizeB
const package1 = new Package()
const prizeA = new Prize(100)
const prizeB = new Prize(150)
然後把 prizeA 和 prizeB 放入 package1
package1.add(prizeA)
package1.add(prizeB)
最後,只要呼叫 package1 的 getPrize
方法,就可以计算出奖品的总额
package1.getPrize() // 250
看到这里,你可能会觉得这有什麽难的。到这里的确没有什麽难度,但如果是下面这样包福袋的状况,可能就会有点复杂了:
现在我们有三种福袋,其中
const package1 = new Package()
const package2 = new Package()
const package3 = new Package()
const prizeA = new Prize(100)
const prizeB = new Prize(150)
const prizeC = new Prize(200)
const prizeD = new Prize(250)
package1.add(prizeA)
package1.add(prizeB)
package2.add(prizeC)
package3.add(prizeD)
package2.add(package1)
package3.add(package2)
请问,三种福袋分别的价值是多少呢?
别担心,这里我们只要分别呼叫 getPrize
就可以得到结果罗
package1.getPrize() // 250
package2.getPrize() // 450
package3.getPrize() // 700
合成模式是一种较为特别的模式,他适用在类似树状的物件结构上面。什麽样叫做树状呢?以树来说,就是树枝可以连接到其他小树枝,小树枝可以连到小小树枝,而每一个树枝、小树枝、小小树枝都可以连到树叶。
以刚刚的例子来说,福袋当中可以包含小福袋、小福袋当中可以包含小小福袋,而每一个福袋、小福袋、小小福袋当中,可以放入奖品。
这里会发现,福袋、小福袋、小小福袋有个共同的特徵,就是可以放入其他东西(福袋或奖品),以及计算总金额。而奖品本身虽然无法放入其他东西,但是同样拥有计算总金额的方法。
这里的 Package
类别,我们可以称作 "Composite",而 Prize
可以称作 "Leaf"。
"Composite" 的特点是,可以收集、连接其他不同的 "Composite" 或 "Leaf",但若要执行特定功能,譬如 getPrize
,就会将实际的执行交给底下的 "Composite" 或 "Leaf" 去执行。
以刚刚的例子来说,我们是怎麽计算 package3 的总金额呢?如果仔细来看,package3 当中的 getPrize
会呼叫所有 children 的 getPrize
方法,然後进行加总。所以这里实际上我们呼叫了
以此类推,当我们呼叫了 package2.getPrize() 的时候,实际上也呼叫了
而当我们呼叫了 package1.getPrize() 的时候,实际上呼叫了
最後,所有结果就不断回传,最後计算出 package3 的总金额。
合成模式的优点在於,非常适合像是上面提到这样的树状结构(自己可以组合/合成自己)的状况,在树的每一个解点上面,都可以执行期待中的操作。
在现实世界当中,类似的例子像是工作阶层的分工,譬如一个企业的全球总部下面有各国的国家分部,每个国家分部当中又拥有不同的城市分部,而不管是哪一个阶层、分部,都有同样的财务、会计、法务、营运 ... 等功能,而且都要视情况回报给上一层。
合成模式的缺点在於,使用场景相对狭隘。如果刚刚提到企业的城市分部,和国家分部的运作行为非常不一样的话,我们就无法建立一个共同使用的抽象类别或介面,最後导致其实这棵树当中的每个节点都是不一样类别,也就不是合成模式的行为了
>>: 找LeetCode上简单的题目来撑过30天啦(DAY24)
演算法评估 ### 演算法衡量 效率 渐进符号 EX:O(n) 最差案例 平均案例 平摊分析 问题衡...
昨天我们介绍了props的用法,要注意的是,props是单向数据流,所以只能从上传到下,要由下传到上...
Iteration forEach()、every()、some() 三者的差异在於:他们会对我们...
今天讲GitHub - RxSwiftCommunity/RxGesture,RxGesture是封...
YAML(/ˈjæməl/,尾音类似camel骆驼)是一个可读性高,用来表达资料序列化的格式。YA...