跳轉到內容

組合模式

50% developed
來自華夏公益教科書,開放的書籍,開放的世界

命令模式 計算機科學設計模式
組合模式
裝飾模式

組合設計模式降低了處理以樹形結構表示資料的實現成本。當應用程式對樹進行處理時,通常需要處理對元件的迭代,在樹上的移動,以及分別處理節點和葉子。所有這些都會產生大量的程式碼。假設您需要處理一個檔案系統儲存庫。每個資料夾可以包含檔案或資料夾。為了處理這種情況,您有一個包含檔案或資料夾的專案陣列。現在您需要在整個資料夾樹上實現一個檔案搜尋操作。虛擬碼應該如下所示

method searchFilesInFolders(rootFolder, searchedFileName) is
    input: a list of the content of the rootFolder.
    input: the searchedFileName that should be found in the folders.
    output: the list of encountered files.

  Empty the foundFiles list
  Empty the parentFolders list
  Empty the parentIndices list
  currentFolder := rootFolder
  currentIndex := 0
  Add rootFolder to parentFolders

  while parentFolders is not empty do
    if currentIndex is out of currentFolder then
        currentFolder := last item of parentFolders
        Remove the last item of parentFolders
        currentIndex := last item of parentIndices + 1
        Remove the last item of parentIndices

    else if the item at the currentIndex of the currentFolder is a folder then
      currentFolder := the folder
      Add currentFolder to parentFolders
      Add currentIndex to parentIndices
      currentIndex := 0

    otherwise
      if the name of the file is equal to the searchedFileName then
        Add the file to foundFiles
      Increment currentIndex

  Return the foundFiles

在前面的程式碼中,同一個 while 迴圈的每次迭代都是對一個節點或葉子的處理。在處理完成後,程式碼會移動到下一個要處理的節點或葉子的位置。迴圈中有三個分支。第一個分支在處理完節點的所有子節點後為真,然後它移動到父節點,第二個分支進入子節點,最後一個分支處理葉子(即檔案)。應該儲存位置的記憶體以便返回到樹。這種實現的問題是可讀性差,而且資料夾和檔案的處理是完全分開的。這段程式碼維護起來很麻煩,您需要時刻考慮到整棵樹。資料夾和檔案應該以相同的方式呼叫,所以它們應該是實現相同介面的物件。

UML 中的組合模式。
元件
  • 是對所有元件(包括複合元件)的抽象。
  • 宣告組合中物件的介面。
  • (可選)定義一個介面來訪問元件在遞迴結構中的父節點,並在適當的情況下實現它。
葉子
  • 表示組合中的葉子物件。
  • 實現所有元件方法。
組合模式
  • 表示一個複合元件(包含子節點的元件)。
  • 實現操作子節點的方法。
  • 實現所有元件方法,通常透過將它們委託給它的子節點來實現。

所以現在的實現更像是這樣

interface FileSystemComponent is
  method searchFilesInFolders(searchedFileName) is
      input: the searchedFileName that should be found in the folders.
      output: the list of encountered files.
class File implementing FileSystemComponent is
  method searchFilesInFolders(searchedFileName) is
      input: the searchedFileName that should be found in the folders.
      output: the list of encountered files.

    if the name of the file is equal to the searchedFileName then
      Empty the foundFiles list
      Add the file to foundFiles
      Return the foundFiles

    otherwise
      Return an empty list
class Folder implementing FileSystemComponent is
  field children is
    The list of the direct children.

  method searchFilesInFolders(searchedFileName) is
      input: the searchedFileName that should be found in the folders.
      output: the list of encountered files.

    Empty the foundFiles list

    for each child in children
      Call searchFilesInFolders(searchedFileName) on the child
      Add the result to foundFiles

    Return the foundFiles

正如您所看到的,一個元件可以是一個單獨的物件,也可以是物件的集合。組合模式可以同時表示這兩個條件。在這個模式中,可以開發樹結構來表示部分-整體層次結構。

示例

這種模式最典型的應用就是圖形使用者介面。介面的視窗部件被組織成樹狀結構,對所有視窗部件的操作(調整大小、重新繪製等)都是使用組合設計模式進行處理的。

成本

這種模式是最便宜的模式之一。您可以在每次需要處理資料樹時實現它,而無需擔心。沒有這種模式的錯誤用法。該模式的成本只是處理複合節點的子節點,但這項成本在沒有設計模式的情況下是必需的,而且更昂貴。

建立

您需要建立一個幾乎為空的介面,並實現對複合子節點的管理。這個成本非常低。

維護

您不會陷入系統中。唯一相對昂貴的情況是您必須經常更改應用於整個資料樹的操作。

移除

您應該在移除資料樹時移除該模式。因此您只需要一次性移除所有內容。這個成本非常低。

建議

  • 在類的名稱中加入複合元件這兩個詞,以便向其他開發人員表明該模式的使用。

實現

組合模式的各種示例。

C# 中的實現
using System;
using System.Collections.Generic;
namespace Composite
{
    class Program
    {
        interface IGraphic
        {
            void Print();
        }
        class CompositeGraphic : IGraphic
        {
            private List<IGraphic> child = new List<IGraphic>();
            public CompositeGraphic(IEnumerable<IGraphic> collection)
            {
                child.AddRange(collection);
            }
            public void Print()
            {
                foreach(IGraphic g in child)
                {
                    g.Print();
                }
            }
        }
        class Ellipse : IGraphic
        {
            public void Print()
            {
                Console.WriteLine("Ellipse");
            }
        }
        static void Main(string[] args)
        {
            new CompositeGraphic(new IGraphic[]
                {
                    new CompositeGraphic(new IGraphic[]
                        {
                            new Ellipse(),
                            new Ellipse(),
                            new Ellipse()
                        }),
                    new CompositeGraphic(new IGraphic[]
                        {
                            new Ellipse()
                        })
                }).Print();
        }
    }
}
Common Lisp 中的實現

以下用 Common Lisp 編寫的示例直接從下面的 Java 示例翻譯而來,它實現了一個名為print-graphic的方法,可以用於橢圓,也可以用於元素是列表或橢圓的列表。

(defstruct ellipse) ;; An empty struct.
;; For the method definitions, "object" is the variable,
;; and the following word is the type.
(defmethod print-graphic ((object null))
  NIL)
(defmethod print-graphic ((object cons))
  (print-graphic (first object))
  (print-graphic (rest object)))
(defmethod print-graphic ((object ellipse))
  (print 'ELLIPSE))
(let* ((ellipse-1 (make-ellipse))
       (ellipse-2 (make-ellipse))
       (ellipse-3 (make-ellipse))
       (ellipse-4 (make-ellipse)))
  (print-graphic (cons (list ellipse-1 (list ellipse-2 ellipse-3)) ellipse-4)))
Java 中的實現

以下用 Java 編寫的示例實現了一個圖形類,它可以是一個橢圓,也可以是多個圖形的組合。每個圖形都可以被列印。用 Backus-Naur 形式,

Graphic ::= ellipse | GraphicList
GraphicList ::= empty | Graphic GraphicList

可以擴充套件它來實現其他幾種形狀(矩形等)和方法(平移等)。

import java.util.List;
import java.util.ArrayList;

/** "Component" */
interface Graphic {
    //Prints the graphic.
    public void print();
}

/** "Composite" */
class CompositeGraphic implements Graphic {
    //Collection of child graphics.
    private final List<Graphic> childGraphics = new ArrayList<>();

    //Adds the graphic to the composition.
    public void add(Graphic graphic) {
        childGraphics.add(graphic);
    }
    
    //Prints the graphic.
    @Override
    public void print() {
        for (Graphic graphic : childGraphics) {
            graphic.print();  //Delegation
        }
    }
}

/** "Leaf" */
class Ellipse implements Graphic {
    //Prints the graphic.
    @Override
    public void print() {
        System.out.println("Ellipse");
    }
}

/** Client */
class CompositeDemo {
    public static void main(String[] args) {
        //Initialize four ellipses
        Ellipse ellipse1 = new Ellipse();
        Ellipse ellipse2 = new Ellipse();
        Ellipse ellipse3 = new Ellipse();
        Ellipse ellipse4 = new Ellipse();

        //Creates two composites containing the ellipses
        CompositeGraphic compositGraphic2 = new CompositeGraphic();
        compositGraphic2.add(ellipse1);
        compositGraphic2.add(ellipse2);
        compositGraphic2.add(ellipse3);
        
        CompositeGraphic compositGraphic3 = new CompositeGraphic();
        compositGraphic3.add(ellipse4);
        
        //Create another graphics that contains two graphics
        CompositeGraphic compositGraphic = new CompositeGraphic();
        compositGraphic.add(compositGraphic2);
        compositGraphic.add(compositGraphic3);

        //Prints the complete graphic (Four times the string "Ellipse").
        compositGraphic.print();
    }
}

以下用 Java 編寫的示例實現了一個圖形類,它可以是一個橢圓,也可以是多個圖形的組合。每個圖形都可以被列印。用代數形式,

       Graphic = ellipse | GraphicList
       GraphicList = empty | Graphic GraphicList

可以擴充套件它來實現其他幾種形狀(矩形等)和方法(平移等)。

/** "Component" */
interface Graphic {
    // Prints the graphic.
    public void print();
}
/** "Composite" */
import java.util.List;
import java.util.ArrayList;
class CompositeGraphic implements Graphic {
    // Collection of child graphics.
    private List<Graphic> mChildGraphics = new ArrayList<Graphic>();
    // Prints the graphic.
    public void print() {
        for (Graphic graphic : mChildGraphics) {
            graphic.print();
        }
    }
    // Adds the graphic to the composition.
    public void add(Graphic graphic) {
        mChildGraphics.add(graphic);
    }
    // Removes the graphic from the composition.
    public void remove(Graphic graphic) {
        mChildGraphics.remove(graphic);
    }
}
/** "Leaf" */
class Ellipse implements Graphic {
    // Prints the graphic.
    public void print() {
        System.out.println("Ellipse");
    }
}
/** Client */
public class Program {
    public static void main(String[] args) {
        // Initialize four ellipses
        Ellipse ellipse1 = new Ellipse();
        Ellipse ellipse2 = new Ellipse();
        Ellipse ellipse3 = new Ellipse();
        Ellipse ellipse4 = new Ellipse();
        // Initialize three composite graphics
        CompositeGraphic graphic = new CompositeGraphic();
        CompositeGraphic graphic1 = new CompositeGraphic();
        CompositeGraphic graphic2 = new CompositeGraphic();
        // Composes the graphics
        graphic1.add(ellipse1);
        graphic1.add(ellipse2);
        graphic1.add(ellipse3);
        graphic2.add(ellipse4);
        graphic.add(graphic1);
        graphic.add(graphic2);
        // Prints the complete graphic (four times the string "Ellipse").
        graphic.print();
    }
}
PHP 中的實現
<?php
/** "Component" */
interface Graphic
{
    /**
     * Prints the graphic
     *
     * @return void
     */
    public function printOut();
}
 
/**
 * "Composite" - Collection of graphical components
 */
class CompositeGraphic implements Graphic
{
    /**
     * Collection of child graphics
     *
     * @var array
     */
    private $childGraphics = array();
 
    /**
     * Prints the graphic
     *
     * @return void
     */
    public function printOut()
    {
        foreach ($this->childGraphics as $graphic) {
            $graphic->printOut();
        }
    }
 
    /**
     * Adds the graphic to the composition
     *
     * @param Graphic $graphic Graphical element
     *
     * @return void  
     */
    public function add(Graphic $graphic)
    {
        $this->childGraphics[] = $graphic;
    }
 
    /**
     * Removes the graphic from the composition
     *
     * @param Graphic $graphic Graphical element
     *
     * @return void
     */
    public function remove(Graphic $graphic)
    {
        if (in_array($graphic, $this->childGraphics)) {
            unset($this->childGraphics[array_search($graphic, $this->childGraphics)]);
        }
    }
}
 
/** "Leaf" */
class Ellipse implements Graphic
{
    /**
     * Prints the graphic
     *
     * @return void
     */
    public function printOut()
    {
        echo "Ellipse";
    }
}
 
/** Client */
 
//Initialize four ellipses
$ellipse1 = new Ellipse();
$ellipse2 = new Ellipse();
$ellipse3 = new Ellipse();
$ellipse4 = new Ellipse();
 
//Initialize three composite graphics
$graphic = new CompositeGraphic();
$graphic1 = new CompositeGraphic();
$graphic2 = new CompositeGraphic();
 
//Composes the graphics
$graphic1->add($ellipse1);
$graphic1->add($ellipse2);
$graphic1->add($ellipse3);
 
$graphic2->add($ellipse4);
 
$graphic->add($graphic1);
$graphic->add($graphic2);
 
//Prints the complete graphic (four times the string "Ellipse").
$graphic->printOut();
Python 中的實現
class Component(object):
    def __init__(self, *args, **kw):
        pass
    def component_function(self): pass
class Leaf(Component):
    def __init__(self, *args, **kw):
        Component.__init__(self, *args, **kw)
    def component_function(self):
        print "some function"
class Composite(Component):
    def __init__(self, *args, **kw):
        Component.__init__(self, *args, **kw)
        self.children = []
   
    def append_child(self, child):
        self.children.append(child)
   
    def remove_child(self, child):
        self.children.remove(child)
    def component_function(self):
        map(lambda x: x.component_function(), self.children)
   
c = Composite()
l = Leaf()
l_two = Leaf()
c.append_child(l)
c.append_child(l_two)
c.component_function()
Ruby 中的實現
module Component
  def do_something
    raise NotImplementedError
  end
end
class Leaf
  include Component
  def do_something
    puts "Hello"
  end
end
class Composite
  include Component
  attr_accessor :children
  def initialize
    self.children = []
  end
  def do_something
    children.each {|c| c.do_something}
  end
  def append_child(child)
    children << child
  end
  def remove_child(child)
    children.delete child
  end
end
composite = Composite.new
leaf_one = Leaf.new
leaf_two = Leaf.new
composite.append_child(leaf_one)
composite.append_child(leaf_two)
composite.do_something


Clipboard

待辦事項
新增更多插圖。


命令模式 計算機科學設計模式
組合模式
裝飾模式


您對本頁有疑問?
在這裡提問


在本圖書上建立新頁面


華夏公益教科書