组合模式(compsite)
组合模式(composite)
1、作用
在树型结构的模型中,有两种节点:叶子节点、中间节点,其中叶子节点不能再接节点,中间节点可以接叶子节点和中间节点。这个模型用组合模式能够很好的实现,在组合模式中分为3个类:component(抽象构件)、composite(容器构件)、leaf(叶子构件)。其中component是composite和leaf共同的抽象基类,这样统一了叶子节点也中间节点的区别,方便用户使用;composite是一个容器构件,可以继续容纳composite和leaf构件;leaf构件不能容纳leaf构件和compsite构件。
文件系统是compsite模式的一个很好的例子:把文件和文件夹抽象成component类、文件夹则为compsite的容器类、文件为leaf类。
2、实现方式
下面是使用组合模式抽象出的文件系统的UML图:
先是将文件(File)和目录(Directory)抽象成目录(Entry)类,类中包含三个函数:
add:这个函数用于添加子节点,Entry中默认实现为抛出异常的形式,这样File继承这个方法后,不需要修改,因为File是叶子节点不能添加子节点,当调用该方法时抛出异常(在File类中,最好还是将这个方法隐藏起来,不对用户可见,覆盖为private的或者定位为delete的)。Directory类中要对这方法进行重写,实现添加子节点的功能。
getName:获取节点名字
printList:打印该节点下子节点的所有名字,在File类中这个函数需要被隐藏,应为File没有子节点。Directory中要重新该函数,因为基类Entry中这个函数是抽象的。
特别需要注意的是,Directory与Entry的关系是组合关系,这样能够保证在Directory中添加Entry(file和Directory),这个结构中,我树做成了双向树,既可以从根节点到叶节点,也可以从叶节点到根节点,目的是为了获取路径(这个功能也可以添加一个路径path变量为Entry的成员),同时可以实现返回上一层目录的功能。
name:文件夹或者文件的名字
parent:文件或者文件夹上一层目录对象指针。
add():添加文件或者文件夹到当前文件夹中,文件的add函数是隐藏起来的。
getName():获取文件或者文件夹的名字。
printList():打印文件夹下所有文件或者文件夹的名字,文件的这个函数是隐藏的。
printPath():打印当前文件夹或者文件所在的路径。
getParent():获取文件或文件夹的上一层目录的Entry对象指针(这个类本应该隐藏的,设置为protected的,但是由于在Directory中要访问Entry对象的parent成员,做不到,不知道还有没有其他的好办法。)
3、C++代码
fileSys.h
#include <vector>
#include <string>
#include <iostream>
#include <exception>
#include <stack>
#ifndef __FILESYS__H__
#define __FILESYS__H__
using namespace std;
class Entry {
public:
Entry(const string &name) : name (name) ,parent(nullptr){}
virtual void add(Entry &) { throw exception(); }
virtual string getName() const { return name; }
virtual void printList() const {};
void setParent(Entry *e) { parent = e; }
virtual void printPath() const {
stack<string> s;
const Entry *node = this;
do {
s.push(node->getName());
node = node->parent;
} while(node != nullptr);
while(!s.empty()) {
cout<<"/"<<s.top();
s.pop();
}
cout<<endl;
}
protected:
string name;
Entry *parent;
};
class File : public Entry {
public:
File(const string &name):Entry(name){}
private:
using Entry::add; // 隐藏add函数,定义为delete的方式是阻止生成默认的构造函数。
using Entry::printList; // 隐藏printLinst函数
};
class Directory : public Entry {
public:
Directory(const string &name):Entry(name){}
void add(Entry &e) {
e.setParent(this); // Directory域中只能访问Entry域中的public成员, 不能直接访问proteted的parent成员
//e.parent = this;
filelist.push_back(e);
}
void printList() const {
for(const auto &v : filelist)
cout<<v.getName()<<endl;
}
private:
vector<Entry> filelist; //(用map存储会更好,其中名字为键,方便获取目录下的某个文件)
};
#endif
test.cc
生成如下目录结构
#include "FileSys.h"
int main() {
Entry *fileSys = new Directory("home");
auto tmp1 = Directory("script");
auto tmp2 = Directory("data");
auto tmp3 = Directory("bin");
auto tmp4 = Directory("lib");
auto tmp5 = File("python.py");
tmp1.add(tmp5);
fileSys->add(tmp1);
fileSys->add(tmp2);
fileSys->add(tmp3);
fileSys->add(tmp4);
fileSys->printList(); // 打印home目录下所有文件和文件夹名
tmp1.printList(); // 打印script目录下的所有文件和文件夹名字
tmp5.printPath(); // 打印python.py的全路径
return 0;
}
输出:
使用了Map的版本
FileSys.h
#include <vector>
#include <string>
#include <iostream>
#include <exception>
#include <stack>
#include <map>
#ifndef __FILESYS__H__
#define __FILESYS__H__
using namespace std;
class Entry {
public:
Entry(const string &name) : name (name) ,parent(nullptr){}
virtual void add(Entry &) { throw exception(); }
virtual string getName() const { return name; }
virtual void printList() const {};
void setParent(Entry *e) { parent = e; }
virtual void printPath() const {
stack<string> s;
const Entry *node = this;
do {
s.push(node->getName());
node = node->parent;
} while(node != nullptr);
while(!s.empty()) {
cout<<"/"<<s.top();
s.pop();
}
cout<<endl;
}
protected:
string name;
Entry *parent;
};
class File : public Entry {
public:
File(const string &name):Entry(name){}
private:
using Entry::add; // 隐藏add函数,定义为delete的方式是阻止生成默认的构造函数。
using Entry::printList; // 隐藏printLinst函数
};
class Directory : public Entry {
public:
Directory(const string &name):Entry(name){}
void add(Entry &e) {
e.setParent(this); // Directory域中只能访问Entry域中的public成员, 不能直接访问proteted的parent成员
auto ret = fileList.insert({e.getName(), e});
if(!ret.second) // 已经存在map中
throw runtime_error(e.getName() + " is in " + name + " Directory");
}
void printList() const {
for(const auto &v : fileList)
cout<<v.first<<endl;
//cout<<v.second.getName()<<endl;
}
private:
map<string,Entry> fileList;
};
#endif
test.cc
#include "FileSys.h"
int main() {
Entry *fileSys = new Directory("home");
auto tmp1 = Directory("script");
auto tmp2 = Directory("data");
auto tmp3 = Directory("bin");
auto tmp4 = Directory("lib");
auto tmp5 = File("python.py");
tmp1.add(tmp5);
fileSys->add(tmp1);
fileSys->add(tmp2);
fileSys->add(tmp3);
fileSys->add(tmp4);
fileSys->printList();
tmp1.printList();
tmp5.printPath();
try {
fileSys->add(tmp1); // 加入相同的文件夹或文件,会产生异常
} catch(runtime_error err) {
cout<<err.what()<<endl;
}
return 0;
}
输出: