组合模式#
如果你发现正在开发的应用似乎存在一种类树形结构,或者你需要实现树状对象结构,此时可以使用组合模式。 换句话说,在组合模式下该结构可以抽象为两个基本元素类型:
- 简单叶节点
- 复杂容器 复杂容器中可以包含简单叶节点,以及其他复杂容器,形成一棵自上而下的递归树。
问题引入#
现在我们需要开发一种订购系统,该订购系统的商品可以抽象为两种形式:盒子
以及产品
。盒子中可以是产品,也可以是其他盒子。形成如下的树形结构:
如果此时我们需要完整计算整个订单的价格,就需要一个一个遍历每个节点,找到最终的产品。但在程序中,我们似乎没有很好的方法,能够使用循环做到这样的事情。因为包含着完全未知的盒子层数以及其他产品的细节信息。
解决问题#
使用组合模式 (composite),组合模式中建议定义一个通用的接口来对盒子
和 产品
进行交互。在该接口中声明一个通用的执行方法(此处为计算总价的方法)。在进行计算总价的时候,我们无需关心具体的类是盒子还是产品,直接调用该执行方法即可递归的完成所有计算。如果是盒子,那么计算方法会”打开”盒子计算所有其中的物品的总价,最终返回。如果是产品,就直接返回该产品的价格。
整体结构#
代码实现#
对于上述场景,简洁代码实现如下:
type Component interface {
computeValue() int64
}
type Product struct {
value int64
}
func (p *Product) computeValue() int64 {
return p.value
}
type Box struct {
Child []Component
}
func (b *Box) computeValue() int64 {
res := int64(0)
// 无需判断具体类型
for _, child := range b.Child {
res += ((child).computeValue())
}
return res
}
goWhen To Implement#
- Use the Composite pattern when you have to implement a tree-like object structure.
- Use the pattern when you want the client code to treat both simple and complex elements uniformly.
How To Implement#
Make sure that the core model of your app can be represented as a tree structure. Try to break it down into simple elements and containers. Remember that containers must be able to contain both *simple elements and other containers.
Declare the component interface with a list of methods that make sense for both simple and complex components.
Create a leaf class to represent simple elements. A program may have multiple different leaf classes.
Create a container class to represent complex elements. In this class, provide an array field for storing references to sub-elements. The array must be able to store both leaves and containers, so make sure it’s declared with the component interface type.
While implementing the methods of the component interface, remember that a container is supposed to be delegating most of the work to sub-elements.
- Finally, define the methods for adding and removal of child elements in the container.
Keep in mind that these operations can be declared in the component interface. This would violate the Interface Segregation Principle because the methods will be empty in the leaf class. However, the client will be able to treat all the elements equally, even when composing the tree.