组合模式
组合设计模式降低了处理以树形结构表示数据的实现成本。当应用程序对树进行处理时,通常需要处理对组件的迭代,在树上的移动,以及分别处理节点和叶子。所有这些都会产生大量的代码。假设您需要处理一个文件系统存储库。每个文件夹可以包含文件或文件夹。为了处理这种情况,您有一个包含文件或文件夹的项目数组。现在您需要在整个文件夹树上实现一个文件搜索操作。伪代码应该如下所示
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 循环的每次迭代都是对一个节点或叶子的处理。在处理完成后,代码会移动到下一个要处理的节点或叶子的位置。循环中有三个分支。第一个分支在处理完节点的所有子节点后为真,然后它移动到父节点,第二个分支进入子节点,最后一个分支处理叶子(即文件)。应该存储位置的内存以便返回到树。这种实现的问题是可读性差,而且文件夹和文件的处理是完全分开的。这段代码维护起来很麻烦,您需要时刻考虑到整棵树。文件夹和文件应该以相同的方式调用,所以它们应该是实现相同接口的对象。
- 组件
- 是对所有组件(包括复合组件)的抽象。
- 声明组合中对象的接口。
- (可选)定义一个接口来访问组件在递归结构中的父节点,并在适当的情况下实现它。
- 叶子
- 表示组合中的叶子对象。
- 实现所有组件方法。
- 组合模式
- 表示一个复合组件(包含子节点的组件)。
- 实现操作子节点的方法。
- 实现所有组件方法,通常通过将它们委托给它的子节点来实现。
所以现在的实现更像是这样
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
正如您所看到的,一个组件可以是一个单独的对象,也可以是对象的集合。组合模式可以同时表示这两个条件。在这个模式中,可以开发树结构来表示部分-整体层次结构。
示例
这种模式最典型的应用就是图形用户界面。界面的窗口部件被组织成树状结构,对所有窗口部件的操作(调整大小、重新绘制等)都是使用组合设计模式进行处理的。
成本
这种模式是最便宜的模式之一。您可以在每次需要处理数据树时实现它,而无需担心。没有这种模式的错误用法。该模式的成本只是处理复合节点的子节点,但这项成本在没有设计模式的情况下是必需的,而且更昂贵。
创建
您需要创建一个几乎为空的接口,并实现对复合子节点的管理。这个成本非常低。
维护
您不会陷入系统中。唯一相对昂贵的情况是您必须经常更改应用于整个数据树的操作。
移除
您应该在移除数据树时移除该模式。因此您只需要一次性移除所有内容。这个成本非常低。
建议
- 在类的名称中加入复合和组件这两个词,以便向其他开发人员表明该模式的使用。
实现
组合模式的各种示例。
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 编写的示例直接从下面的 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 编写的示例实现了一个图形类,它可以是一个椭圆,也可以是多个图形的组合。每个图形都可以被打印。用 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
/** "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();
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()
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