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