组合模式(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成员,做不到,不知道还有没有其他的好办法。)

img

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

生成如下目录结构

img

#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;
}

输出:

posted @ 2023-09-07 18:08  下夕阳  阅读(8)  评论(0编辑  收藏  举报