圣诞大减价开始了!

复合

意图

复合是一种结构设计模式,它允许您将对象组合到树形结构中,然后像处理单个对象一样使用这些结构。

复合设计模式

问题

只有当应用程序的核心模型可以表示为树时,使用Composite模式才有意义。

例如,假设你有两种类型的对象:产品而且盒子.一个盒子可以包含几个产品还有一些更小的盒子.这些小盒子也可以装一些产品或者更小盒子等等。

假设您决定创建一个使用这些类的排序系统。订单可能包含没有任何包装的简单产品,以及塞满产品的盒子……还有其他盒子。你如何确定这样一个订单的总价?

一种复杂次序的结构

一份订单可能包括各种各样的产品,包装在盒子里,这些盒子又包装在更大的盒子里,等等。整个结构看起来像一棵倒立的树。

你可以尝试直接的方法:打开所有的盒子,检查所有的产品,然后计算总数。这在现实世界中是可行的;但在程序中,这并不像运行循环那么简单。你必须知道产品而且盒子你要事先检查盒子的嵌套级别和其他烦人的细节。所有这些都使得直接的方法要么太尴尬,要么甚至不可能。

解决方案

复合模式建议您使用产品而且盒子通过一个公共接口,该接口声明了一个计算总价的方法。

这种方法是如何工作的?对于产品,它只会返回产品的价格。对于一个盒子,它会检查盒子里的每一件商品,询问价格,然后返回这个盒子的总价。如果其中一件物品是一个较小的盒子,这个盒子也会开始检查它的内容,直到计算出所有内部组件的价格。一个盒子甚至会在最终价格上增加一些额外的成本,比如包装成本。

复合模式建议的解决方案

Composite模式允许您在对象树的所有组件上递归地运行一个行为。

这种方法的最大好处是您不需要关心组成树的具体对象类。你不需要知道一个物体是一个简单的产品还是一个复杂的盒子。您可以通过公共接口对它们一视同仁。当您调用一个方法时,对象本身会沿着树向下传递请求。

真实的模拟

一个军事结构的例子

一个军事结构的例子。

大多数国家的军队都是等级制的。军队由几个师组成;师是一个旅的集合,旅是由排组成的,排可以被分解成班。最后,小队是由真正的士兵组成的一小群人。命令在层级的顶层下达,然后传递到每一层,直到每个士兵都知道需要做什么。

结构

复合设计模式的结构
  1. 组件接口描述了对于树的简单元素和复杂元素都通用的操作。

  2. 是树中没有子元素的基本元素。

    通常,叶组件最终会完成大部分实际工作,因为它们没有任何人可以委托这些工作。

  3. 容器(又名复合)是一个元素,它有子元素:叶子或其他容器。容器不知道其子容器的具体类。它只能通过组件接口处理所有子元素。

    容器在收到请求后,将工作委托给它的子元素,处理中间结果,然后将最终结果返回给客户机。

  4. 客户端通过组件接口使用所有元素。因此,客户端可以以相同的方式处理树中的简单或复杂元素。

伪代码

在本例中,复合模式使您可以在图形编辑器中实现几何形状的堆叠。

复合例子的结构

几何形状编辑器示例。

CompoundGraphic类是一个容器,它可以包含任意数量的子形状,包括其他复合形状。复合形状具有与简单形状相同的方法。然而,复合形状不是自己做一些事情,而是递归地将请求传递给它的所有子形状,并“总结”结果。

客户端代码通过所有形状类共用的单个接口处理所有形状。因此,客户端不知道它是在处理简单形状还是复合形状。客户端可以使用非常复杂的对象结构,而无需与构成该结构的具体类耦合。

//组件接口为组合的简单和复杂对象声明了通用操作。//叶子类代表一个组合的结束对象。叶子对象不能有任何子对象。通常,执行实际工作的是叶子//对象,而复合对象只将//委托给它们的子组件。类Dot实现图形是字段x, y构造函数Dot(x, y){…}方法move(x, y)是这样的。X += X,这个。y += y method draw() is //在X和y处画一个点。//所有的组件类都可以扩展其他组件。类Circle extends Dot是字段半径构造函数Circle(x, y,半径){…}方法draw() is //在X和Y处画一个半径为r的圆。//复合类表示可能有子组件的复杂组件。复合对象通常将实际的//工作委托给它们的子对象,然后“总结”结果。 class CompoundGraphic implements Graphic is field children: array of Graphic // A composite object can add or remove other components // (both simple or complex) to or from its child list. method add(child: Graphic) is // Add a child to the array of children. method remove(child: Graphic) is // Remove a child from the array of children. method move(x, y) is foreach (child in children) do child.move(x, y) // A composite executes its primary logic in a particular // way. It traverses recursively through all its children, // collecting and summing up their results. Since the // composite's children pass these calls to their own // children and so forth, the whole object tree is traversed // as a result. method draw() is // 1. For each child component: // - Draw the component. // - Update the bounding rectangle. // 2. Draw a dashed rectangle using the bounding // coordinates. // The client code works with all the components via their base // interface. This way the client code can support simple leaf // components as well as complex composites. class ImageEditor is field all: CompoundGraphic method load() is all = new CompoundGraphic() all.add(new Dot(1, 2)) all.add(new Circle(5, 3, 10)) // ... // Combine selected components into one complex composite // component. method groupSelected(components: array of Graphic) is group = new CompoundGraphic() foreach (component in components) do group.add(component) all.remove(component) all.add(group) // All components will be drawn. all.draw()

适用性

  • 当您必须实现树状对象结构时,请使用Composite模式。

  • Composite模式为您提供了两种共享公共接口的基本元素类型:简单叶和复杂容器。容器可以由叶容器和其他容器组成。这样就可以构造类似树的嵌套递归对象结构。

  • 当您希望客户端代码统一处理简单和复杂元素时,请使用该模式。

  • Composite模式定义的所有元素都共享一个公共接口。使用这个接口,客户端不必担心它所处理的对象的具体类。

如何实施

  1. 确保应用程序的核心模型可以用树形结构表示。试着把它分解成简单的元素和容器。记住,容器必须既能包含简单元素,也能包含其他容器。

  2. 用一组方法声明组件接口,这些方法对简单组件和复杂组件都有意义。

  3. 创建一个叶类来表示简单元素。一个程序可以有多个不同的叶类。

  4. 创建一个容器类来表示复杂元素。在这个类中,提供一个数组字段用于存储对子元素的引用。数组必须能够存储叶子和容器,因此确保它是用组件接口类型声明的。

    在实现组件接口的方法时,请记住容器应该将大部分工作委托给子元素。

  5. 最后,定义在容器中添加和删除子元素的方法。

    请记住,这些操作可以在组件接口中声明。这就违反了界面分离原理因为这些方法在叶类中将是空的。然而,客户端将能够平等地对待所有元素,甚至在组合树时也是如此。

利与弊

    • 您可以更方便地使用复杂的树结构:使用多态和递归。
    • 打开/关闭原则.你可以在应用程序中引入新的元素类型,而不破坏现有的代码,现在这些代码与对象树一起工作。
    • 为功能差异太大的类提供公共接口可能很困难。在某些情况下,您需要过度概括组件接口,使其更难理解。

代码示例

Baidu
map