Chapter8—组合模式
组合模式
1 概念
组合模式是将部分对象组成树形结构,表示部分与整体之间的一种层次结构,整体包含部分,部分又由次级部分组成,这样的层次关系具有一致性。一个整体可以拆分出独立的模块或功能,这样独立的模块又可以继续拆分出类似的模块或功能,通过继承的方式将这些模块和功能按照层次关系组合成树形结构,在这个树形结构中,从根节点出发,遍历其所有的子节点进行处理,一视同仁的处理每个节点,忽略层次的差异,从而达到对整个系统进行处理。
2 意图(Intent)
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite模式使得用户对单个对象和组合对象的使用具有一致性。简单工厂模式是指一个工厂对象通过其自身方法中的形参变量决定创建产品的类型。
3 动机(Motivate)
组合模式有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
4 类图结构
5 角色定义
-
Component(组件接口):所有复合节点和叶子节点的高层抽象,定义出需要对组件操作的接口标准。
-
Composite(复合组件):包含多个子组件对象(可以是复合组件或叶端组件)的复合型组件,并实现组件接口中定义的操作方法。
-
Leaf(叶端组件):不包含子组件的终端组件,同样实现组件接口中定义的操作方法。
-
Client(客户端):按所需的层级关系部署相关对象并操作组件接口所定义的接口。
6 程序示例
文件列表
示例类图
基础节点类,也就是组件的接口类
/*** @brief 基础节点类*/
class BaseNode
{
public:BaseNode(const string& strName);virtual ~BaseNode() = default;public:virtual void Add(BaseNode* pChildNode) = 0;virtual void Display(int nSpace); // 展示本节点的内容virtual void Release() = 0;protected:string m_strName;
};BaseNode::BaseNode(const string &strName): m_strName(strName)
{// ...
}void BaseNode::Display(int nSpace)
{QString strSpace;for (int i = 0; i < nSpace; ++i)strSpace += " ";qDebug().noquote() << strSpace << m_strName.c_str();
}
文件夹类,也就是复合组件
/*** @brief 文件夹类*/
class Folder: public BaseNode
{
public:Folder(const string& strName);~Folder();public:void Add(BaseNode* pChildNode) override;void Display(int nSpace) override;void Release() override;private:vector<BaseNode*> m_vecChildNode;
};Folder::Folder(const string &strName): BaseNode(strName)
{}Folder::~Folder()
{qDebug() << "Folder Release!";
}void Folder::Add(BaseNode* pChildNode)
{m_vecChildNode.push_back(pChildNode);
}void Folder::Display(int nSpace)
{BaseNode::Display(nSpace); // 调用父类接口先展示本目录名称,再遍历本目录里面的内容for (BaseNode* pNode : m_vecChildNode)pNode->Display(nSpace + 1); // 子节点的空格需要+1, 这样显示一层层的效果
}void Folder::Release()
{for (BaseNode* pNode : m_vecChildNode)pNode->Release(); // 先释放其子文件/目录delete this; // 再释放自己
}
文件夹类,也就是叶端组件类
/*** @brief 文件类*/
class File: public BaseNode
{
public:File(const string& strName);~File();public:void Add(BaseNode* pChildNode) override;void Release() override;
};File::File(const string &strName): BaseNode(strName)
{}File::~File()
{qDebug() << "File Release!";
}void File::Add(BaseNode* pChildNode)
{Q_UNUSED(pChildNode);
}void File::Release()
{delete this;
}
客户端使用场景
void ClientTest()
{BaseNode* pDriveD = new Folder("D:");BaseNode* pDoc = new Folder("Document");pDoc->Add(new File("Resume.doc"));pDoc->Add(new File("Project.ppt"));pDriveD->Add(pDoc);BaseNode* pMusic = new Folder("Movie");BaseNode* pHongKong = new Folder("HongKong");pHongKong->Add(new File("A.mp4"));pHongKong->Add(new File("B.mp4"));pHongKong->Add(new File("C.mp4"));BaseNode* pAmerica = new Folder("America");pAmerica->Add(new File("AA.mp4"));pAmerica->Add(new File("BB.mp4"));pMusic->Add(pHongKong);pMusic->Add(pAmerica);pDriveD->Add(pMusic);pDriveD->Display(0);pDriveD->Release(); // 释放内存
}
程序输出
D:DocumentResume.docProject.pptMovieHongKongA.mp4B.mp4C.mp4AmericaAA.mp4BB.mp4File Release!
File Release!
Folder Release!
File Release!
File Release!
File Release!
Folder Release!
File Release!
File Release!
Folder Release!
Folder Release!
Folder Release!
水果盘
类图结构
抽象构建类MyElement(抽象类)
/*** @brief 抽象构建类MyElement(抽象类)*/
class MyElement
{
public:MyElement() = default;virtual ~MyElement() = default;public:virtual void Eat() = 0;virtual void Add(MyElement* pElement) = 0;virtual void Remove(MyElement* pElement) = 0;
};
叶子构建类Apple(苹果类)
/*** @brief 叶子构建类Apple(苹果类)*/
class Apple: public MyElement
{
public:Apple() = default;~Apple() = default;public:void Eat() override;void Add(MyElement* pElement) override;void Remove(MyElement* pElement) override;
};void Apple::Eat()
{qDebug() << "吃苹果!";
}void Apple::Add(MyElement *pElement)
{}void Apple::Remove(MyElement *pElement)
{}
叶子构建类Banana(香蕉类)
/*** @brief 叶子构建类Banana(香蕉类)*/
class Banana: public MyElement
{
public:Banana() = default;~Banana() = default;public:void Eat() override;void Add(MyElement* pElement) override;void Remove(MyElement* pElement) override;
};void Banana::Eat()
{qDebug() << "吃香蕉!";
}void Banana::Add(MyElement *pElement)
{}void Banana::Remove(MyElement *pElement)
{}
叶子构建类Pear(梨子类)
/*** @brief 叶子构建类Pear(梨子类)*/
class Pear: public MyElement
{
public:Pear() = default;~Pear() = default;public:void Eat() override;void Add(MyElement* pElement) override;void Remove(MyElement* pElement) override;
};void Pear::Eat()
{qDebug() << "吃梨子!";
}void Pear::Add(MyElement *pElement)
{}void Pear::Remove(MyElement *pElement)
{}
容器构建类Plate(水果盘类)
/*** @brief 容器构建类Plate(水果盘类)*/
class Plate: public MyElement
{
public:Plate() = default;~Plate() = default;public:void Eat() override;void Add(MyElement* pElement) override;void Remove(MyElement* pElement) override;private:vector<MyElement*> m_vecElement;
};void Plate::Eat()
{for (auto& pElement : m_vecElement)pElement->Eat();
}void Plate::Add(MyElement *pElement)
{m_vecElement.push_back(pElement);
}void Plate::Remove(MyElement *pElement)
{auto pFind = std::find(m_vecElement.begin(), m_vecElement.end(), pElement);if (pFind != m_vecElement.end())m_vecElement.erase(pFind);
}
测试函数
/*** @brief 测试函数*/
void ClientTest()
{MyElement* pAppleA = new Apple();MyElement* pPear = new Pear();MyElement* pPlateA = new Plate();pPlateA->Add(pAppleA);pPlateA->Add(pPear);MyElement* pBananaA = new Banana();MyElement* pBananaB = new Banana();MyElement* pPlateB = new Plate();pPlateB->Add(pBananaA);pPlateB->Add(pBananaB);MyElement* pAppleB = new Apple();MyElement* pPlateC = new Plate();pPlateC->Add(pPlateA);pPlateC->Add(pPlateB);pPlateC->Add(pAppleB);pPlateC->Eat();delete pAppleA;delete pPear;delete pPlateA;delete pBananaA;delete pBananaB;delete pPlateB;delete pAppleB;delete pPlateC;
}
7 思考小结
组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构建也很容易,因为它让客户忽略了层次的差异,而它的结构又是动态的,提供了对象管理的灵活接口,因此组合模式可以方便地对层次结构进行控制。组合模式定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。