js常用設計模式6-組合模式 1,命令模式和組合模式的聯合應用 2,分析一下組合模式 3,更強大的宏命令 4,組合模式的實例-掃描文件夾 5,一些需要注意的地方 6,葉對象引用父對象 7,小結

組合模式和命令模式有點像,命令模式是一個個小的指令,而組合模式是一些小指令組合成的大指令

1,命令模式和組合模式的聯合應用

試想這麼一個場景:我們回家之後先關門,然後開電腦,最後打開QQ
關門,開電腦,開QQ是三個命令,現在我們用MacroCommand函數把他們組合起來,得到一個對象macroCommand ,通過macroCommand 來操作所有的命令。

  • macroCommand 被稱作組合對象,它實際上是真正的命令數組commandList的“代理”。當然macroCommand不是代理,它只負責傳遞請求給真正的命令函數。
  • 關門,開電腦,開QQ都是葉對象。
var closeDoorCommand = {
  execute: function () {
    console.log('關門')
  }
}

var openPCCommand = {
  execute: function () {
    console.log('開電腦')
  }
}

var openQQCommand = {
  execute: function () {
    console.log('開QQ')
  }
}

var MacroCommand = function () {
  return {
    commandList: [],
    add: function (command) {
      this.commandList.push(command)
    },
    execute: function () {
      for (var i = 0, command; command = this.commandList[i++];) {
        command.execute()
      }
    }
  }
}

var macroCommand = MacroCommand()

//宏命令包含了一組子命令,形成了樹形結構
macroCommand.add(closeDoorCommand)
macroCommand.add(openPCCommand)
macroCommand.add(openQQCommand)

//macroCommand:它是一個組合對象,表現爲命令,但實際上只是一組真正命令的代理
macroCommand.execute()

2,分析一下組合模式

在上面的例子中macroCommand是 closeDoorCommand、openPCCommand、openQQCommand這三個命令的組合對象,它們有一個共同點:都有execute函數。這個函數代表了組合和單個命令的一致性。
組合模式將對象組合成樹形結構,以表示“部分-整體”的結構層次,加上execute實現的一致性,使得用戶在使用的時候,可以忽略組合和單個命令的不同,直接調用就完事了。
對於一個遙控器而言,當我們按下一個鍵時,只關注它帶來的結果,而不需要在意這個操作調用了多少命令,只要它有execute,那麼它就是好命令。

3,更強大的宏命令

現在我們的遙控器,包含了關門、開電腦、開QQ這三個功能。現在我們需要一個超級遙控器,能控制家裏所有的電器,包括:

  • 打開空調
  • 打開電視和音響
  • 關門、開電腦、開QQ
    這時候我們會發現,之前的macroCommand現在變成了一個組合對象的一部分。
/**
 * 正題
 * 組合模式就是組合了一堆命令,可以統一調用,而忽略單個命令
 */

//更強大的宏命令--只要有execute,你就是他的一員,進行深度遍歷

var MacroCommand = function () {
  return {
    commandList: [],
    add: function (command) {
      this.commandList.push(command)
    },
    execute: function () {
      for (var i = 0, command; command = this.commandList[i++];) {
        command.execute()
      }
    }
  }
}

var openAcCommand = {
  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)


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(openAcCommand)
macroCommand.add(macroCommand1)
macroCommand.add(macroCommand2)

macroCommand.execute()


錯誤處理:
//缺點:葉節點可能會使用add方法,需要錯誤處理
var openAcCommand = {
  execute: function () {
    console.log('開空調')
  },
  add: function () {
    throw new Error('葉節點不能添加子對象')
  }
}
openAcCommand.add()

4,組合模式的實例-掃描文件夾

文件夾和文件之間的聯繫,非常適合用組合模式來描述(個人覺得dom節點的關係也很適合)。文件夾裏既可以包含文件,又可以包含其他文件夾,最終形成了一棵樹。組合模式對於文件夾應用有以下兩個好處:

  • 複製文件夾所有內容的時候,只需要複製最外層的文件夾就行了
  • 用殺毒軟件掃描文件夾的時候,不需要關心文件夾裏面有多少文件夾或者文件,直接掃描最上層文件夾就可以了。
    現在,我們先定義文件夾Folder和文件File這兩個類:
/*********** Folder   ***********/
var Folder = function (name) {
  this.name = name
  this.files = []
}
Folder.prototype.add = function (file) {
  this.files.push(file)
}
Folder.prototype.scan = function () {
  console.log('開始掃描文件夾:' + this.name)
  for (var i = 0, file; file = this.files[i++];) {
    file.scan()
  }
}

/*********** Folder   ***********/
var File = function (name) {
  this.name = name
}
File.prototype.add = function () {
  throw new Error('文件下面不能添加文件')
}
File.prototype.scan = function () {
  console.log('開始掃描文件:' + this.name)
}

然後,創建文件夾和文件,將其組合成一棵樹,這個結構就是我們硬盤裏的文件目錄結構:

var mainFolder = new Folder('學習資料')
var folder1 = new Folder('vue資料')
var folder2 = new Folder('react資料')

var file1 = new File('vue api')
var file2 = new File('vue 生命週期')
var file3 = new File('react-router')
var file4 = new File('設計模式')


folder1.add(file1)
folder1.add(file2)
folder2.add(file3)

mainFolder.add(folder1)
mainFolder.add(folder2)
mainFolder.add(file4)

mainFolder.scan()

通過這個例子我們可以看到,當我們需要遍歷整個文件夾時,只需要調用最上層文件夾的scen方法:mainFolder.scan()。在新增文件時,用戶也不需要關心它們具體是文件夾還是文件,直接添加進去就完事了。

5,一些需要注意的地方

  • 組合模式是聚合關係,而不是父子關係,因爲葉對象(最開始的單個命令)不是組合對象的子類。組合對象可以把請求委託給它的所有葉對象,因爲它們有相同的接口(和dom樹很像)。
  • 組合模式的應用場景:必須要每個節點都有相同的接口,以及操作一致性。比如之前的例子,現在每個文件節點都有scan方法,但是如果有的文件有刪除方法,有的沒有,那麼組合模式就不適用,要麼都有,那麼都沒有。
  • 雙向映射關係:一個節點只能屬於一個組合對象,不能同時屬於兩個,比如一個文件,只能有一個直接的文件夾來包含,不可能同時有兩個。如果一個人屬於開發組,同時又屬於測試組,這種交叉情況就不適用於組合模式。
  • 組合模式的對象關係和職責鏈模式很像。

6,葉對象引用父對象

之前的例子中,只能從父對象到葉對象,反過來是不行的。但是,當我們要刪除某個文件的時候,我們需要知道它具體屬於哪個文件夾,實際是從上層文件夾中刪除文件的。
首先改寫Folder類和File類,增加parent屬性,在add函數中設置parent:

var Folder = function (name) {
  this.name = name
  this.files = []
}
Folder.prototype.add = function (file) {
  //添加父節點引用
  file.parent = this
  this.files.push(file)
}
Folder.prototype.scan = function () {
  console.log('開始掃描文件夾:' + this.name)
  for (var i = 0, file; file = this.files[i++];) {
    file.scan()
  }
}

給文件夾添加刪除功能。
如果this.parent === null,要麼它就是根節點,要麼是還沒有添加到樹中,這種情況先暫時return,不作處理。
否則的話,該文件夾有父節點,那麼就遍歷父節點的所有子節點,找個需要刪除的子節點,直接刪除。

//添加刪除功能。
Folder.prototype.delete = function () {
  if (this.parent === null) {
    return
  }
  for (var i = 0, files = this.parent.files; file = files[i]; i++) {
    if (file === this) {
      files.splice(i, 1)
    }
  }
}

File類的實現基本一致:

var File = function (name) {
  this.name = name
  this.parent = null
}
File.prototype.add = function () {
  throw new Error('文件下面不能添加文件')
}
File.prototype.scan = function () {
  console.log('開始掃描文件:' + this.name)
}
File.prototype.delete = function () {
  if (this.parent === null) {
    return
  }
  for (var i = 0, files = this.parent.files; file = files[i]; i++) {
    if (file === this) {
      files.splice(i, 1)
    }
  }
}

最後,我們來try一try:

var folder = new Folder('學習資料')
var folder1 = new Folder('vue')
var folder2 = new Folder('react')

var file1 = new File('vue api')
var file2 = new File('react api')
var file3 = new File('設計模式')

folder1.add(file1)
folder2.add(file2)
folder.add(folder1)
folder.add(folder2)
folder.add(file3)


folder1.delete()
folder.scan()

7,小結

何時使用組合模式:

  1. 表示對象的“部分-整體”結構層次。組合模式構造了一棵樹,來表示對象的“部分-整體”結構,用戶不需要知道樹到底有多少層,只需要請求最頂層的節點,就可以對整棵樹做統一的操作。
  2. 樹中的所有對象都一致。客戶不需要關注一個節點到底是組合對象還是葉對象,因爲它們有相同的方法,能用就完事了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章