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