組合模式就是用小的子對象來構建更大的對象, 將對象組合成樹形結構, 以表示 「部分-總體」 的層次結構.函數
位於底層最小的對象, 咱們在這裏稱爲葉對象, 由葉對象組成的組合對象咱們稱爲分支對象, 位於最頂層的根對象在這裏咱們也稱爲分支對象. 不過它們只是結構上呈現出父子關係.post
組合模式最大的好處能夠一致性地對待分支對象和葉對象, 這也就要求了它們有着相同的接口. 在JavaScript中理解策略模式中, 咱們對員工進行考覈, 劃分了甲乙丙三個等級. 如今, 公司決定結合等級以資鼓勵.ui
如下全部代碼參見compositeMode.this
現有考覈人員:spa
考覈項目\考覈人 | 等級 | 月薪 |
---|---|---|
person_1 | 甲 | 6k |
person_2 | 乙 | 9k |
person_3 | 乙 | 4k |
person_4 | 乙 | 5k |
person_5 | 丙 | 6k |
person_6 | 丙 | 5k |
person_7 | 丙 | 6k |
person_8 | 丙 | 4k |
person_9 | 甲 | 8k |
const persons = [
{ scale: '甲', salary: 6000, company: 'JavaScript' },
{ scale: '乙', salary: 9000, company: 'JavaScript' },
{ scale: '乙', salary: 4000, company: 'JavaScript' },
{ scale: '乙', salary: 5000, company: 'JavaScript' },
{ scale: '丙', salary: 6000, company: 'JavaScript' },
{ scale: '丙', salary: 5000, company: 'JavaScript' },
{ scale: '丙', salary: 6000, company: 'JavaScript' },
{ scale: '丙', salary: 4000, company: 'JavaScript' },
{ scale: '丙', salary: 9000, company: 'JavaScript' },
{ scale: '甲', salary: 6000, company: 'JavaScript' },
]
複製代碼
等級與月薪相關, 分別爲3倍月薪、2倍月薪、1倍月薪.3d
等級 | 甲 | 乙 | 丙 |
---|---|---|---|
月薪倍率 | 3 | 2 | 1 |
const scaleMap = {
'甲': 3,
'乙': 2,
'丙': 1
}
複製代碼
到底須要拿出多少錢獎勵呢? 甲乙丙三組人分別佔比多少呢? 針對這樣的需求, 咱們嘗試着用組合模式計算一下.code
上面也提到了, 組合模式中有兩類對象. 一個是分支對象, 一個是葉對象. 分支對象是葉對象的集合, 保存着葉對象的引用, 能夠操做葉對象(好比添加, 執行). 而葉對象只要暴露接口便可. 如今咱們從簡單的開始, 造一個生產葉對象的函數.cdn
const leaf = params => {
return {
...params,
expense() {
return scaleMap[params.scale] * params.salary
}
}
}
複製代碼
分支對象就稍微麻煩些了, 由於須要管理葉對象.對象
const branch = params => {
return {
...params,
members: [],
add(item) {
this.members.push(item)
},
expense() {
return this.members.reduce((sum, item) => {
return sum + item.expense()
}, 0)
}
}
}
複製代碼
expense()
的方法不管是在分支對象仍是葉對象上, 或者說不清楚是分支對象仍是葉對象, 咱們均可以獲得相應的結果. 在分支對象裏, 保存着葉對象的引用, 咱們能夠對葉對象進行任何操做.blog
組合模式就是這麼簡單, 剩下的就是如何 JSON 數據轉換成井井有條的結構了.
const convertData = array => {
const branchList = [];
const branchObjs = [];
array.forEach(item => {
let leafObj = leaf(item);
if (!branchList.includes(item.scale)) {
let params = {
scale: item.scale
}
let branchObj = branch(params)
branchObjs.push(branchObj)
branchList.push(item.scale)
}
branchObjs.filter(obj => obj.scale == item.scale)[0].add(leafObj);
})
return branchObjs;
}
console.log('branchObjs', convertData(persons))
const scaleA = convertData(persons)[0]
const scaleB = convertData(persons)[1]
const scaleC = convertData(persons)[2]
console.log('甲', scaleA.expense())
console.log('乙', scaleB.expense())
console.log('丙', scaleC.expense())
複製代碼
最終結果展現
若是咱們想知道公司的總支出, 也能夠把 scaleA, scaleB, scaleC 組合在一塊兒做爲公司的一子集.
const company = branch();
company.add(scaleA);
company.add(scaleB);
company.add(scaleC);
company.expense()
複製代碼
不難發現, 咱們能夠隨意組裝對象而不會影響到其它節點, 任何一個集合或個體都可以單獨運行. 咱們也能夠根據須要去組合更復雜的結構. 可是, 若是經過組合模式建立了太多的對象, 那麼這些對象可能會讓系統負擔不起.
組合模式對象形式上至少有兩種, 分支對象和葉對象.
分支對象和葉對象擁有相同的接口, 且對一組葉對象操做具備同步一致性.
組合模式是 HAS-A (聚合)關係, 不是 IS-A.