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



















 
 
 
 
 
 
 