面試官:說(shuō)說(shuō)你對(duì)組合模式的理解?應(yīng)用場(chǎng)景?
一、是什么
組合模式,又叫 “部分整體” 模式,將對(duì)象組合成樹(shù)形結(jié)構(gòu),以表示 “部分-整體” 的層次結(jié)構(gòu)。通過(guò)對(duì)象的多態(tài)性表現(xiàn),使得用戶(hù)對(duì)單個(gè)對(duì)象和組合對(duì)象的使用具有一致性
如下面的代碼:
- var closeDoorCommand = {
- execute: function () {
- console.log('關(guān)門(mén)');
- }
- };
- var openPcCommand = {
- execute: function () {
- console.log('開(kāi)電腦');
- }
- };
- var openQQCommand = {
- execute: function () {
- console.log('登錄 QQ');
- }
- };
- var MacroCommand = function () {
- return {
- commandsList: [],
- add: function (command) {
- this.commandsList.push(command);
- },
- execute: function () {
- for (var i = 0, command; command = this.commandsList[i++];) {
- command.execute();
- }
- }
- }
- };
- var macroCommand = MacroCommand();
- macroCommand.add(closeDoorCommand);
- macroCommand.add(openPcCommand);
- macroCommand.add(openQQCommand);
- macroCommand.execute();
上述是命令模式的一個(gè)應(yīng)用,macroCommand命令叫做組合對(duì)象,其包含了closeDoorCommand、openPcCommand、openQQCommand三個(gè)葉對(duì)象
macroCommand 的 execute 方法里,并不執(zhí)行真正的操作,而是遍歷它所包含的葉對(duì)象,把真正的 execute 請(qǐng)求委托給這些葉對(duì)象
二、應(yīng)用場(chǎng)景
組合模式應(yīng)樹(shù)形結(jié)構(gòu)而生,所以組合模式的使用場(chǎng)景就是出現(xiàn)樹(shù)形結(jié)構(gòu)的地方:
- 「命令分發(fā):」 只需要通過(guò)請(qǐng)求樹(shù)的最頂層對(duì)象,便能對(duì)整棵樹(shù)做統(tǒng)一的操作。在組合模式中增加和刪除樹(shù)的節(jié)點(diǎn)非常方便,并且符合開(kāi)放-封閉原則;
- 「統(tǒng)一處理:」 統(tǒng)一對(duì)待樹(shù)中的所有對(duì)象,忽略組合對(duì)象和葉對(duì)象的區(qū)別
如將上述例子稍復(fù)雜,當(dāng)我們點(diǎn)擊按鈕時(shí),出發(fā)一系列操作(打開(kāi)空調(diào),打開(kāi)電視,打開(kāi)音響)其中打開(kāi)電視和打開(kāi)音響是一組組合對(duì)象,如下代碼:
- <button id=button>按我</button>
- <script>
- var MacroCommand = function () {
- return {
- commandsList: [],
- add: function (command) {
- this.commandsList.push(command);
- },
- execute: function () {
- for (var i = 0, command; command = this.commandsList[i++];) {
- command.execute();
- }
- }
- }
- };
- var openAcCommend = {
- execute: function () {
- console.log('打開(kāi)空調(diào)');
- }
- }
- // 電視和音響一起打開(kāi)
- var openTvCommand = {
- execute: function () {
- console.log('打開(kāi)電視');
- }
- }
- var openSoundCommand = {
- execute: function () {
- console.log('打開(kāi)音響');
- }
- }
- var macroCommand1 = MacroCommand()
- macroCommand1.add(openTvCommand)
- macroCommand1.add(openSoundCommand)
- // 關(guān)門(mén)、開(kāi)電腦、登QQ的命令
- var closeDoorCommand = {
- execute: function () {
- console.log('關(guān)門(mén)');
- }
- };
- var openPcCommand = {
- execute: function () {
- console.log('開(kāi)電腦');
- }
- };
- var openQQCommand = {
- execute: function () {
- console.log('登錄 QQ');
- }
- };
- var macroCommand2 = MacroCommand();
- macroCommand2.add(closeDoorCommand);
- macroCommand2.add(openPcCommand);
- macroCommand2.add(openQQCommand);
- // 所有命令組合成一個(gè)超級(jí)命令
- var macroCommand = MacroCommand();
- macroCommand.add(openAcCommend)
- macroCommand.add(macroCommand1)
- macroCommand.add(macroCommand2)
- // 給超級(jí)遙控器綁定命令
- var setCommand = (function (command) {
- document.getElementById('button').onclick = function () {
- command.execute()
- }
- })(macroCommand)
- </script>
組合模式的透明性使得發(fā)起請(qǐng)求的客戶(hù)不用去顧忌樹(shù)中組合對(duì)象和葉對(duì)象的區(qū)別,但它們?cè)诒举|(zhì)上是有區(qū)別的。
組合對(duì)象可以擁有葉子節(jié)點(diǎn),葉對(duì)象下面就沒(méi)有子節(jié)點(diǎn),所以我們可能會(huì)有一些誤操作,比如試圖往葉對(duì)象中添加子節(jié)點(diǎn)
解決方案就是給葉對(duì)象也增加 add 方法,并且在調(diào)用這個(gè)方法時(shí),拋出一個(gè)異常來(lái)及時(shí)提醒用戶(hù),如下:
- var MacroCommand = function () {
- return {
- commandsList: [],
- add: function (command) {
- this.commandsList.push(command);
- },
- execute: function () {
- for (var i = 0, command; command = this.commandsList[i++];) {
- command.execute();
- }
- }
- }
- };
- var openAcCommend = {
- execute: function () {
- console.log('打開(kāi)空調(diào)');
- },
- add: function() {
- throw new Error('葉對(duì)象不能添加子節(jié)點(diǎn)')
- }
- }
三、總結(jié)
組合模式常使用樹(shù)形方式創(chuàng)建對(duì)象,如下圖:
特點(diǎn)如下:
- 表示 “部分-整體” 的層次結(jié)構(gòu),生成 "樹(shù)葉型" 結(jié)構(gòu)
- 一致操作性,樹(shù)葉對(duì)象對(duì)外接口保存一致(操作與數(shù)據(jù)結(jié)構(gòu)一致)
- 自上而下的的請(qǐng)求流向,從樹(shù)對(duì)象傳遞給葉對(duì)象
- 調(diào)用頂層對(duì)象,會(huì)自行遍歷其下的葉對(duì)象執(zhí)行
參考文獻(xiàn)
https://www.runoob.com/design-pattern/composite-pattern.html
https://segmentfault.com/a/1190000019773556
https://juejin.cn/post/6995851145490989070