赞
踩
微信公众号:幼儿园的学霸
Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly.
将对象组合成树形结构以表示 “部分-整体” 的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
组合模式 一般用来描述整体与部分的关系,它将对象组织到树形结构中,最顶层的节点称为 根节点,根节点下面可以包含 树枝节点 和 叶子节点,树枝节点下面又可以包含 树枝节点 和 叶子节点。如下图所示:
由上图可以看出,其实 根节点
和 树枝节点
本质上是同一种数据类型(蓝色圆圈),可以作为容器使用;而 叶子节点
与 树枝节点
在语义上不属于同一种类型,但是在 组合模式
中,会把 树枝节点
和 叶子节点
认为是同一种数据类型(用同一接口定义),让它们具备一致行为。这样,在 组合模式 中,整个树形结构中的对象都是同一种类型,带来的一个好处就是客户无需辨别 树枝节点 还是 叶子节点,而是可以直接进行操作,给客户使用带来极大的便利。
组合模式的设计动机:如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无须进行区分,可以对他们进行一致的处理。
组合模的核心:借助同一接口,使叶子节点和树枝节点的操作具备一致性。
组合模式的本质:统一叶子对象和组合对象/树枝对象
组合模式包含以下主要角色:
Add()、Remove()、GetChild()
等方法。组合模式在代码具体实现上,有透明组合模式和安全组模式两种实现。
透明组合模式:
透明组合模式把组合(树枝节点、树叶节点)使用的方法全部放到抽象根节点(Component)中,让不同层次的节点(树枝节点、树叶节点)的结构都具备相同的行为。其UML类图如下:
在透明组合模式中,由于抽象根节点声明了所有子类中的全部方法,所以产生的好处是客户端无须分辨是树叶节点(Leaf)和树枝节点(Composite),它们具备完全一致的接口,对客户端来说是透明的;但其缺点是树叶节点会继承得到一些它所不需要的方法(管理子类操作的方法,如树叶节点本来没有 Add()、Remove() 及 GetChild() 方法,但是由于被继承了下来,因此需要进行实现,实现为空方法或者抛异常),这与设计模式中接口隔离原则相违背。
接口隔离原则:1)客户端不应该依赖它不需要的接口;2)类间的依赖关系应该建立在最小的接口上
安全组合模式:
抽象根节点(Component)只规定各个层次的最基础的一致行为,而把组合(树枝节点)本身的方法(如管理子类对象的添加、删除等)放到自身当中。其UML类图如下:
在该方式中由于将树枝节点特有的行为放到自身当中,树叶节点没有对子对象的管理方法,带来的好处是接口定义职责清晰,符合设计模式的接口隔离原则和单一职责原则,避免了上一种方式的安全性问题;但是由于树枝节点和树叶节点有不同的接口,使得客户端调用时需要区分树枝节点(Composite)和树叶节点(Leaf),这样才能正确处理各个层次的操作,客户端无法依赖抽象(Component),失去了透明性,违背了设计模式的依赖倒置原则。
单一职责原则:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责
依赖倒置原则:程序要依赖于抽象接口,不要依赖于具体实现。或,要对抽象(抽象类)进行编程,不要对实现(具体的实现类)进行编程
场景:北京总公司,上海分公司,北京财务部,上海财务部的公司结构。
以北京总部,上海分公司、广州分公司、成都分公司为例。 透明组合模式的代码如下:
#include <bits/stdc++.h> // //组合模式:透明组合模式 // // 抽象的部件类描述将来所有部件共有的行为 class Component { public: Component(const std::string &name) : m_strCompname(name) { } virtual ~Component() = default;//基类:虚析构函数 virtual void Operation() = 0; virtual void Add(const std::shared_ptr<Component> pComponent) = 0; virtual void Remove(const std::shared_ptr<Component> pComponent) = 0; //为了讲解方便,此处用了裸指针(Raw Pointer),实际项目建议修改为智能指针 virtual std::vector<std::shared_ptr<Component>> *GetChild() = 0; virtual const std::string &GetName() const { return m_strCompname; } //打印节点及子节点 //共有行为及实现,该函数放在基类中即可 virtual void Print(int level = 0) { std::cout << std::string(4*level,'-') <<m_strCompname << std::endl; //打印其下节点 auto childs = this->GetChild(); if (childs) for (auto child:*childs) child->Print(level + 1); }; protected: std::string m_strCompname; }; //树叶节点 class Leaf : public Component { public: Leaf(const std::string &name) : Component(name) { } void Operation() override { std::cout << "I'm " << m_strCompname << std::endl; } // 叶节点没有Add功能,但这样做能使接口具备一致性,这就是透明方式, // 如果不加入Add和Remove方法,那就是安全方式。 void Add(const std::shared_ptr<Component> pComponent) override {} void Remove(const std::shared_ptr<Component> pComponent) override {} std::vector<std::shared_ptr<Component>> *GetChild() override { return nullptr; } }; //树枝节点 class Composite : public Component { public: Composite(const std::string &name) : Component(name) { } ~Composite() {} void Operation() override { std::cout << "I'm " << m_strCompname << std::endl; } void Add(std::shared_ptr<Component> pComponent) override { m_vecComp.emplace_back(pComponent); } void Remove(std::shared_ptr<Component> pComponent) override { for (auto it = m_vecComp.begin(); it != m_vecComp.end(); ++it) { if ((*it)->GetName() == pComponent->GetName()) { m_vecComp.erase(it); break; } } } std::vector<std::shared_ptr<Component>> *GetChild() override { if (m_vecComp.size()) return &m_vecComp; return nullptr; } private: std::vector<std::shared_ptr<Component>> m_vecComp; }; int main(int argc, char *argv[]) { std::shared_ptr<Component> pNode = std::make_shared<Composite>("Beijing Head Office");//北京总部 std::shared_ptr<Component> pNodeHr = std::make_shared<Leaf>("Beijing Department1"); std::shared_ptr<Component> pSubNodeSh = std::make_shared<Composite>("Shanghai Branch"); std::shared_ptr<Component> pSubNodeCd = std::make_shared<Composite>("Chengdu Branch"); std::shared_ptr<Component> pSubNodeGz = std::make_shared<Composite>("Guangzhou Branch"); pNode->Add(pNodeHr);//叶子节点 pNode->Add(pSubNodeSh);//树枝节点 上海分部 pNode->Add(pSubNodeCd);//树枝节点 成都分部 pNode->Add(pSubNodeGz);//树枝节点 广州分部 pNode->Print(); std::cout<<std::string(50,'-')<<std::endl; //上海分部节点下面添加自己的叶子节点和树枝节点 std::shared_ptr<Component> pSubNodeShHr = std::make_shared<Leaf>("Shanghai Department1"); std::shared_ptr<Component> pSubNodeShCg = std::make_shared<Leaf>("Shanghai Purchasing Department"); std::shared_ptr<Component> pSubNodeShXs = std::make_shared<Leaf>("Shanghai Sales department"); std::shared_ptr<Component> pSubNodeShZb = std::make_shared<Leaf>("Shanghai Quality supervision Department"); pSubNodeSh->Add(pSubNodeShHr); pSubNodeSh->Add(pSubNodeShCg); pSubNodeSh->Add(pSubNodeShXs); pSubNodeSh->Add(pSubNodeShZb); pNode->Print(); std::cout<<std::string(50,'-')<<std::endl; // 公司不景气,需要关闭上海质量监督部门 pSubNodeSh->Remove(pSubNodeShZb); pNode->Print(); std::cout<<std::string(50,'-')<<std::endl; return 0; //运行结果如下: //Beijing Head Office //----Beijing Department1 //----Shanghai Branch //----Chengdu Branch //----Guangzhou Branch //-------------------------------------------------- //Beijing Head Office //----Beijing Department1 //----Shanghai Branch //--------Shanghai Department1 //--------Shanghai Purchasing Department //--------Shanghai Sales department //--------Shanghai Quality supervision Department //----Chengdu Branch //----Guangzhou Branch //-------------------------------------------------- //Beijing Head Office //----Beijing Department1 //----Shanghai Branch //--------Shanghai Department1 //--------Shanghai Purchasing Department //--------Shanghai Sales department //----Chengdu Branch //----Guangzhou Branch //-------------------------------------------------- }
场景
系统对象层次具备整体和部分,呈树形结构,且要求具备统一行为(如树形菜单,操作系统目录结构,公司组织架构等);
希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象时。
注意事项
有时候系统需要遍历一个树枝结构的子构件很多次,这时候可以考虑把遍历子构件的结构存储在父构件里面作为缓存(类似我上面写的Print打印节点函数)。
客户端尽量不要直接调用树叶类中的方法,而是借用其父类(Graphics)的多态性完成调用,这样可以增加代码的复用性。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。