使你可以将对象组合到树结构中,以表示部分整体层次结构。复合可以使客户端统一对待单个对象和对象组成。
Agnes在沃尔玛工作了一年多;她一开始担任的是库存助理,最近被提升为库存业务员。Agnes作为库存业务员的主要工作是进行每日库存记录。在上班的第三天,经理很担心地与Agnes接触。“Agnes,你记录的玩具车库存不准确,并不是所有的盒子里面都有相同数量的汽车。这是因为有些玩具车比其他玩具车大,所以盒子里装的玩具车就少了。”向经理道歉后,Agnes重新上班了。她是这项工作的新手,她意识到这项工作比她想象的要难,她不得不想出一种准确记录箱子的方法。
以下是产品(Product
)类代码表示的样子:
class Product
{
private $_name = null;
public function __construct($name)
{
$this->_name = $name;
}
public function getName()
{
echo $this->_name;
}
}
以下是箱子(Box
)类代码表示的样子:
class Box
{
private $_products = array();
public function addProduct($products)
{
$this->_products = $products;
}
public function getProducts()
{
echo $this->_product;
}
}
以下是Agnes工作的代码表示:
代码语言:javascript复制class InventoryClerk
{
public function recordProducts(Box $box)
{
$products = $box->getProducts();
foreach($products as $product) {
$product->getName();
}
}
}
整个库存过程似乎非常简单。但是,事实并非如此。在大多数情况下,一个大盒子由十几个小盒子组成,在某些情况下,产品是用一个小盒子包装的。对于Agnes而言,这是一项艰巨的任务,并且通常需要花费更多的时间,因为她必须将所有内容整理好,并根据包装盒的内容和产品数量为包装盒创建不同的类别。这使她的工作比我应有的繁琐。
解决此问题的一种可能方法是在代码上放置一些条件。但是,产品的包装可能会在将来发生变化,并且无法说明即将推出的产品的实际内容。这使得这个可能的解决方案不切实际,并且代码难以维护。
Agnes需要更好的解决方案,一个易于维护的可持续解决方案。
这就是复合模式(Composite Pattern
)适合应用的时候。在我们的案例中,对象是一个整体层次结构。盒子可能包含盒子或产品。我们可以使InventoryClerk
类使用复合模式(Composite Pattern
)统一处理Product
对象和Box
对象。
在复合模式中。有四个要素:
- Component(组件):这是既要实现
Leaf
又要实现Composite
的接口。此类定义客户端使用的抽象功能。在我们的例子中,函数是getName()
。 - Leaf:顾名思义,它是没有子类的类。它实现了如上所述的
Component
接口。在我们的例子中,Leaf
是我们的产品Product
类。 - Composite:复合抽象的
Component
类。因此,除了实现Component
接口之外,它还可以复合具体的Product
类,Composite
类或两者。 - Client(客户端):它通过调用
Component
类中定义的通用函数来统一处理Leaf
类和Composite
类。
下面,我们来重构以上的类。首先,我们需要一个产品组件接口ProductComponent
:
interface ProductComponent
{
public function getName();
}
重构产品Product
类,它要实现刚刚定义的产品组件接口ProductComponent
:
class Product implements ProductComponent
{
private $_name = null;
public function __construct($name)
{
$this->_name = $name;
}
public function getName()
{
echo $this->_name;
}
}
重构箱子Box
类,除了实现ProductComponent
接口之外,它还需要具有添加,删除和获取ProductComponent
的功能:
class Box implements ProductComponent
{
private $_productComponents = array();
public function getName()
{
$productComponents = $this->getProductComponents();
foreach($productComponents as $productComponent) {
$productComponent->getName();
}
}
public function addProductComponent(ProductComponent $productComponent)
{
$this->_productComponents[] = $productComponent;
}
public function removeProductComponent($i)
{
unset($this->_productComponents[$i]);
}
public function getProductComponents()
{
return $this->_productComponents;
}
}
最后,重构库存检验类InventoryClerk
;它将变得非常简洁:
class InventoryClerk
{
public function recordProducts(ProductComponent $productComponent)
{
$productComponent->getName();
}
}
现在,我们已经将复合模式Composite Pattern
应用于我们的案例,我们的代码变得更加易于维护,并且也很灵活。无论一个盒子中有多少个盒子,或者无论这些盒子的大小和内容是否不同,我们的代码都可以使用。
让我们以两个玩具产品分别包装在一个小盒子中为例,然后将两个小盒子放入一个大盒子中:
代码语言:javascript复制$productHelicopter = new Product('toy helicopter');
$productCar = new Product('toy car');
$smallBoxForHelicopter = new Box();
$smallBoxForHelicopter->addProductComponent($productHelicopter);
$smallBoxForCar = new Box();
$smallBoxForCar->addProductComponent($productCar);
$bigBox = new Box();
$bigBox->addProductComponent($smallBoxForHelicopter);
$bigBox->addProductComponent($smallBoxForCar);
对于库存检查,要记录大箱子,就像以下这么简单:
代码语言:javascript复制class InventoryClerk
{
public function recordProducts(ProductComponent $productComponent)
{
$productComponent->getName();
}
}
在我们的示例中,“复合模式(Composite Pattern
)”使我们能够将对象(产品Product
对象和Box
对象)组合为树形结构,以表示部分整体层次结构。复合(Composite
)使客户端(InventoryClerk
对象)可以对单个对象(Product
对象和Box
对象)和对象组成进行统一处理(通过ProductComponent
抽象)。