一、组合模式概念
组合模式可以将一组对象组织成树形结构,以表示一种“部分 - 整体”的层次结构。因此,组合模式是一种用于树形结构数据的设计模式。组合模式跟之前讲的面向对象设计中的“组合关系(通过组合来组装两个类)”是两回事。
相比于前面说的几种设计模式,组合模式是一种用的比较少的模式,数据或者说业务对象必须能表示成树形结构才能用到组合模式,比如用来表示目录结构、部门子部门和员工、订单与下游采购单/出库单/入库单等这样的场景、产品一级二级三级分组等。
下面直接上例子,假设有个需求:设计一个类来表示文件系统中的目录,能方便地实现下面这些功能:
动态地添加、删除某个目录下的子目录或文件;
统计指定目录下的文件个数;
统计指定目录下的文件总大小。
<?php class FileSystemNode { private $path; private $isFile; // 是否为文件 private $subNodes = []; // 当前节点的子节点 private $father; // 当前节点的父节点 private $fileNum ; // 当前文件下的文件个数,包含自身 private $fileSize; // 当前文件大小,对于空目录而言为0 public function __construct($path, $isFile) { $this->path = $path; $this->isFile = $isFile; $this->fileNum = 1; $this->fileSize = $this->isFile ? getFileSize($this->path) : 0; protected function setFather(FileSystemNode $father){ $this->father = $father; $this->refreshFather($this->countSizeOfFiles(), $this->countNumOfFiles()); protected function removeFather(){ $this->refreshFather(-$this->countSizeOfFiles(), -$this->countNumOfFiles()); $this->father = null; // 更新父节点的文件数量和文件大小 public function refreshFather($changeSize, $changeNum){ if(!$this->father){ return; $this->father->setFileSize($this->father->countSizeOfFiles() + $changeSize); $this->father->setFileNum($this->father->countNumOfFiles() + $changeNum); $this->father->refreshFather($changeSize, $changeNum); // 递归 public function setFileNum($num){ $this->fileNum = $num; public function setFileSize($size){ $this->fileSize = $size; public function countNumOfFiles() { return $this->isFile ? $this->fileNum : $this->fileNum-1; public function countSizeOfFiles() { return $this->fileSize; public function getPath() { return $this->path; public function addSubNode(FileSystemNode $fileOrDir) { $this->subNodes[] = $fileOrDir; $fileOrDir->setFather($this); public function removeSubNode(FileSystemNode $fileOrDir) { foreach($this->subNodes as $i => $subNode){ if($subNode->getPath() == $fileOrDir->getPath()){ unset($this->subNodes[$i]); $fileOrDir->removeFather(); break; // 遍历本目录下所有的文件路径(这里使用树的前序遍历) public function iterate(){ echo $this->getPath() . "\n"; foreach($this->subNodes as $subNode){ $subNode->iterate(); }
从上面的例子来看,只要满足以下条件就算符合组合模式:
1、类中包含一个数组属性,该数组中存放者多个和本类属于同一实例的其他实例,表示子节点。
2、类中的A方法需要委托子节点(或者父节点)调用子节点(或者父节点)的A方法,比如上述例子中的 iterate()方法。
组合模式的设计思路,与其说是一种设计模式,倒不如说是对业务场景的一种数据结构和算法的抽象。其中,数据可以表示成树这种数据结构,业务逻辑可以通过在树上的递归遍历算法来实现。
组合模式将一组对象组织成树形结构,将单个对象和组合对象都看做树中的节点,以统一处理逻辑,并且它利用树形结构的特点,递归地处理每个子树,依次 简化代码实现 。
使用组合模式的前提在于,你的业务场景必须能够表示成树形结构。所以,组合模式的应用场景也比较局限,它并不是一种很常用的设计模式。