相关文章推荐
  • 面向对象和设计模式(一) 面向对象、设计原则、设计模式和重构概述
  • 面向对象和设计模式(二) 面向对象四大特性之封装性/抽象性/继承性/多态性 和 类与类之间6种交互关系
  • 面向对象和设计模式(三) 接口类和抽象类的区别与共性和面向对象编程避坑点
  • 面向对象和设计模式(四) 使用组合代替继承——防止继承带来的类数量爆炸增长
  • 面向对象和设计模式(五) 贫血模型和充血模型
  • 面向对象和设计模式(六) 设计原则之单一职责原则 SRP 与 里氏替换原则 LSP
  • 面向对象和设计模式(七)设计原则之开闭原则 OCP(Open Closed Principle)
  • 面向对象和设计模式(八)设计原则之接口隔离原则 ISP(Interface Segregation Principle)
  • 面向对象和设计模式(九)SOLID原则之依赖倒置原则 DIP(Dependency Inversion Principle)
  • 面向对象和设计模式(十)代码如何解耦 与 迪米特法则 LOD(Law of Demeter,The Least Knowledge Principle)
  • 面向对象和设计模式(十一)单例模式以及单例模式的并发安全问题
  • 面向对象和设计模式(十二)工厂模式
  • 面向对象和设计模式(十三)建造者模式——PHP语言实现
  • 面向对象和设计模式(十四)原型模式——PHP语言实现
  • 面向对象和设计模式(十五)代理模式与动态代理模式——PHP语言实现
  • 面向对象和设计模式(十六)桥接模式——PHP语言实现
  • 面向对象和设计模式(十七)装饰器模式——PHP语言实现
  • 面向对象和设计模式(十八)适配器模式——PHP语言实现
  • 面向对象和设计模式(十九)支持树形结构的组合模式——PHP语言实现
  • 面向对象和设计模式(二十) 节省内存的享元模式——PHP语言实现
  • 面向对象和设计模式(二十一) 联动逻辑如何处理?观察者模式——PHP语言实现
  • 面向对象和设计模式(二十二) 代码中如何留下扩展点之模板模式和策略模式——PHP语言实现
  • 面向对象和设计模式(二十三) 对象与数据的流水线处理之职责链模式——PHP实现
  • 面向对象和设计模式(二十四) 如何实现有限状态机之状态模式——PHP实现
  • 一、组合模式概念

    组合模式可以将一组对象组织成树形结构,以表示一种“部分 - 整体”的层次结构。因此,组合模式是一种用于树形结构数据的设计模式。组合模式跟之前讲的面向对象设计中的“组合关系(通过组合来组装两个类)”是两回事。


    相比于前面说的几种设计模式,组合模式是一种用的比较少的模式,数据或者说业务对象必须能表示成树形结构才能用到组合模式,比如用来表示目录结构、部门子部门和员工、订单与下游采购单/出库单/入库单等这样的场景、产品一级二级三级分组等。


    下面直接上例子,假设有个需求:设计一个类来表示文件系统中的目录,能方便地实现下面这些功能:

    动态地添加、删除某个目录下的子目录或文件;

    统计指定目录下的文件个数;

    统计指定目录下的文件总大小。

    <?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()方法。


    组合模式的设计思路,与其说是一种设计模式,倒不如说是对业务场景的一种数据结构和算法的抽象。其中,数据可以表示成树这种数据结构,业务逻辑可以通过在树上的递归遍历算法来实现。

    组合模式将一组对象组织成树形结构,将单个对象和组合对象都看做树中的节点,以统一处理逻辑,并且它利用树形结构的特点,递归地处理每个子树,依次 简化代码实现


    使用组合模式的前提在于,你的业务场景必须能够表示成树形结构。所以,组合模式的应用场景也比较局限,它并不是一种很常用的设计模式。




    更多内容请关注微信公众号
    zbpblog微信公众号
     
    推荐文章