• [设计模式] 设计模式课程(十七)--组合模式


    概述

    • 数据结构模式
      • 常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大地破坏组件的复用
      • 将这些特定数据模式封装在内部,对外提供统一的接口,来实现与特定数据结构无关的访问
      • 典型模式:Composite, Iterator, Chain of Resposibility
    • 属于结构模式(按目的划分)  
    • 动机:软件在某些情况下,客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、扩展性等弊端
    • 如何将“客户代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?
    • 定义:将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性(稳定)
    • 采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化成“一对一”的关系,使得客户代码可以一致地(复用)处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器
    • 将“客户代码与复杂的对象容器结构”结构是Composite的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的内部实现结构——发生依赖,从而更能“应对变化”
    • Composite模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率
    • 如何计算一个复杂订单的总价
    • 采用通用接口与产品和盒子进行交互,在接口中声明一个计算总价的方法
    • 对于产品,直接返回价格,对于盒子,遍历其中所有项目,询问每个项目的价格,然后返回盒子总价格
    • 简单叶子节点和复杂容器实现共享接口,容器中可包含叶子节点和其他容器,从而可构建树状嵌套递归对象
    • 所有元素共用一个接口,客户端不必在意其使用的对象的具体类

      

    场景

    • 部分、整体场景,如树形菜单、文件、文件夹的管理

    示例1

    • 34-36:多态调用,将内部数据结构封装
     1 #include <iostream>
     2 #include <list>
     3 #include <string>
     4 #include <algorithm>
     5 
     6 using namespace std;
     7 
     8 class Component
     9 {
    10 public:
    11     virtual void process() = 0;
    12     virtual ~Component(){}
    13 };
    14 
    15 //树节点
    16 class Composite : public Component{
    17     
    18     string name;
    19     list<Component*> elements;
    20 public:
    21     Composite(const string & s) : name(s) {}
    22     
    23     void add(Component* element) {
    24         elements.push_back(element);
    25     }
    26     void remove(Component* element){
    27         elements.remove(element);
    28     }
    29     
    30     void process(){
    31         
    32         //1. process current node
    33         
    34         //2. process leaf nodes
    35         for (auto &e : elements)
    36             e->process(); //多态调用
    37          
    38     }
    39 };
    40 
    41 //叶子节点
    42 class Leaf : public Component{
    43     string name;
    44 public:
    45     Leaf(string s) : name(s) {}
    46             
    47     void process(){
    48         //process current node
    49     }
    50 };
    51 
    52 void Invoke(Component & c){
    53     //...
    54     c.process();
    55     //...
    56 }
    57 
    58 int main()
    59 {
    60 
    61     Composite root("root");
    62     Composite treeNode1("treeNode1");
    63     Composite treeNode2("treeNode2");
    64     Composite treeNode3("treeNode3");
    65     Composite treeNode4("treeNode4");
    66     Leaf leat1("left1");
    67     Leaf leat2("left2");
    68     
    69     root.add(&treeNode1);
    70     treeNode1.add(&treeNode2);
    71     treeNode2.add(&leaf1);
    72     
    73     root.add(&treeNode3);
    74     treeNode3.add(&treeNode4);
    75     treeNode4.add(&leaf2);
    76     
    77     process(root);
    78     process(leaf2);
    79     process(treeNode3);
    80   
    81 }
    View Code

     

    示例2

      1 #include <algorithm>
      2 #include <iostream>
      3 #include <list>
      4 #include <string>
      5 #include <cstdlib>
      6 using namespace std;
      7 /**
      8  * The base Component class declares common operations for both simple and
      9  * complex objects of a composition.
     10  */
     11 class Component {
     12   /**
     13    * @var Component
     14    */
     15  protected:
     16   Component *parent_;
     17   /**
     18    * Optionally, the base Component can declare an interface for setting and
     19    * accessing a parent of the component in a tree structure. It can also
     20    * provide some default implementation for these methods.
     21    */
     22  public:
     23   virtual ~Component() {}
     24   void SetParent(Component *parent) {
     25     this->parent_ = parent;
     26   }
     27   Component *GetParent() const {
     28     return this->parent_;
     29   }
     30   /**
     31    * In some cases, it would be beneficial to define the child-management
     32    * operations right in the base Component class. This way, you won't need to
     33    * expose any concrete component classes to the client code, even during the
     34    * object tree assembly. The downside is that these methods will be empty for
     35    * the leaf-level components.
     36    */
     37   virtual void Add(Component *component) {}
     38   virtual void Remove(Component *component) {}
     39   /**
     40    * You can provide a method that lets the client code figure out whether a
     41    * component can bear children.
     42    */
     43   virtual bool IsComposite() const {
     44     return false;
     45   }
     46   /**
     47    * The base Component may implement some default behavior or leave it to
     48    * concrete classes (by declaring the method containing the behavior as
     49    * "abstract").
     50    */
     51   virtual std::string Operation() const = 0;
     52 };
     53 /**
     54  * The Leaf class represents the end objects of a composition. A leaf can't have
     55  * any children.
     56  *
     57  * Usually, it's the Leaf objects that do the actual work, whereas Composite
     58  * objects only delegate to their sub-components.
     59  */
     60 class Leaf : public Component {
     61  public:
     62   std::string Operation() const override {
     63     return "Leaf";
     64   }
     65 };
     66 /**
     67  * The Composite class represents the complex components that may have children.
     68  * Usually, the Composite objects delegate the actual work to their children and
     69  * then "sum-up" the result.
     70  */
     71 class Composite : public Component {
     72   /**
     73    * @var SplObjectStorage
     74    */
     75  protected:
     76   std::list<Component *> children_;
     77 
     78  public:
     79   /**
     80    * A composite object can add or remove other components (both simple or
     81    * complex) to or from its child list.
     82    */
     83   void Add(Component *component) override {
     84     this->children_.push_back(component);
     85     component->SetParent(this);
     86   }
     87   /**
     88    * Have in mind that this method removes the pointer to the list but doesn't
     89    * frees the
     90    *     memory, you should do it manually or better use smart pointers.
     91    */
     92   void Remove(Component *component) override {
     93     children_.remove(component);
     94     component->SetParent(nullptr);
     95   }
     96   bool IsComposite() const override {
     97     return true;
     98   }
     99   /**
    100    * The Composite executes its primary logic in a particular way. It traverses
    101    * recursively through all its children, collecting and summing their results.
    102    * Since the composite's children pass these calls to their children and so
    103    * forth, the whole object tree is traversed as a result.
    104    */
    105   std::string Operation() const override {
    106     std::string result;
    107     for (const Component *c : children_) {
    108       if (c == children_.back()) {
    109         result += c->Operation();
    110       } else {
    111         result += c->Operation() + "+";
    112       }
    113     }
    114     return "Branch(" + result + ")";
    115   }
    116 };
    117 /**
    118  * The client code works with all of the components via the base interface.
    119  */
    120 void ClientCode(Component *component) {
    121   // ...
    122   std::cout << "RESULT: " << component->Operation();
    123   // ...
    124 }
    125 
    126 /**
    127  * Thanks to the fact that the child-management operations are declared in the
    128  * base Component class, the client code can work with any component, simple or
    129  * complex, without depending on their concrete classes.
    130  */
    131 void ClientCode2(Component *component1, Component *component2) {
    132   // ...
    133   if (component1->IsComposite()) {
    134     component1->Add(component2);
    135   }
    136   std::cout << "RESULT: " << component1->Operation();
    137   // ...
    138 }
    139 
    140 /**
    141  * This way the client code can support the simple leaf components...
    142  */
    143 
    144 int main() {
    145   Component *simple = new Leaf;
    146   std::cout << "Client: I've got a simple component:
    ";
    147   ClientCode(simple);
    148   std::cout << "
    
    ";
    149   /**
    150    * ...as well as the complex composites.
    151    */
    152 
    153   Component *tree = new Composite;
    154   Component *branch1 = new Composite;
    155 
    156   Component *leaf_1 = new Leaf;
    157   Component *leaf_2 = new Leaf;
    158   Component *leaf_3 = new Leaf;
    159   branch1->Add(leaf_1);
    160   branch1->Add(leaf_2);
    161   Component *branch2 = new Composite;
    162   branch2->Add(leaf_3);
    163   tree->Add(branch1);
    164   tree->Add(branch2);
    165   std::cout << "Client: Now I've got a composite tree:
    ";
    166   ClientCode(tree);
    167   std::cout << "
    
    ";
    168 
    169   std::cout << "Client: I don't need to check the components classes even when managing the tree:
    ";
    170   ClientCode2(tree, simple);
    171   std::cout << "
    ";
    172 
    173   delete simple;
    174   delete tree;
    175   delete branch1;
    176   delete branch2;
    177   delete leaf_1;
    178   delete leaf_2;
    179   delete leaf_3;
    180 
    181   return 0;
    182 }
    View Code

    联系

    • 桥接、状态、策略、适配器模式的接口非常类似,都基于组合模式
    • 创建复杂组合树时使用生成器模式,使其构造步骤以递归的方式进行
    • 通常与责任链模式结合使用,叶组件接收到请求后,可一直传递到对象树的底部
    • 可用迭代器模式遍历组合树
    • 可用访问者模式对组合树进行操作
    • 可用享元模式实现组合树的共享叶节点,以节省内存
    • 和装饰模式的结构图很像,都依赖递归组合来组织无限数目的对象,不同在于,装饰模式只有一个子组件,且为封装对象添加了额外的职责,而组合仅对子节点的结果进行了求和
    • 可使用原型模式复制复杂结构,而非从零开始构造
  • 相关阅读:
    《构建之法(第三版)》第一章学习总结
    20189220余超 2019年密码与安全新技术讲座-课程总结报告
    学号20189220 2018-2019-2 《密码与安全新技术专题》第六周作业
    学号20189220余超 2018-2019-2 《密码与安全新技术专题》第七周作业
    20189200余超 2018-2019-2 移动平台应用开发实践作项目代码分析
    2018-2019-2学号20189220余超《移动平台应用程序开发实践》课程总结
    20189200余超 2018-2019-2 移动平台应用开发实践第十二周作业
    20189220余超 团队博客——阅读软件app
    20189200余超 2018-2019-2 移动平台应用开发实践第十一周作业
    20189200余超 2018-2019-2 移动平台应用开发实践第十周作业
  • 原文地址:https://www.cnblogs.com/cxc1357/p/12320329.html
Copyright © 2020-2023  润新知