質問

I'm trying to create a folder/file system similar to the ones used in most OSes. Basically I've figured out that I should use three classes; File, Folder, and a common base class. Let's call it Common for the sake of creativity. Here's how I thought the headers for those three should look like:

common.h

class Common {
    string m_name; // all files and folders have a name
    Folder* m_parent; // all files and folders can have a parent

public:
    virtual void open() = 0; // executed when folder or file is opened by user
    virtual void draw() const; // all files and folders can be printed
    virtual void setParent(Folder* parent);
    virtual Folder* getParent() const;
};

folder.h

class Folder : public Common {
    vector<Common*> m_children; // folders can contain other files and folders
                                // whereas files cannot

public:
    virtual void open(); // folder opens; basically shows the content
    virtual void draw() const; // folder draws differently
};

file.h

class File : public Common {
    // not really "files", they just call a function when opened
    funcptr m_openAction;

public:
    virtual void open(); // calls m_openAction() when opened
};

As you can see, the problem is that my base class Common should be able to know it's subclass Folder, which is bad behaviour and shouldn't be done (at least according to my teacher). This makes it impossible for me to code the system like I had planned to.

How should such system be designed?

役に立ちましたか?

解決

Your existing design does not require Common to "know its subclass Folder". It just requires the Common header to declare that there is some such class as Folder:

class Folder; // Forward declaration

class Common {
    string m_name; // all files and folders have a name
    Folder* m_parent; // all files and folders can have a parent

public:
    virtual ~Common(); // Don't forget virtual destructor!
    virtual void open() = 0; // executed when folder or file is opened by user
    virtual void draw() const; // all files and folders can be printed
    virtual void setParent(Folder* parent);
    virtual Folder* getParent() const;
};

There is no dependency cycle here.

If for some academic reason you must have a base class that does not even mention any subclass then you can make the polymorphic base class like this:

class Node {
    string m_name; // all files and folders have a name
    Node* m_parent; // all files and folders can have a parent

public:
    virtual ~Node(); // Don't forget virtual destructor!
    virtual void open() = 0; // executed when folder or file is opened by user
    virtual void draw() const; // all files and folders can be printed
    virtual void setParent(Node* parent);
    virtual Node* getParent() const;
};

With this design however the setParent(Node* parent) method will have to include a runtime check that the Node * argument parent is in fact a Folder *, using e.g.

Folder *pf = dynamic_cast<Folder *>(parent);

and in that case it would also require a non-void return type by which to indicate success or failure. This is tortuous as opposed to merely making a declaration of class Folder.

CONTINUED to address OP's follow-up question.

Inside Common's setParent() I have to call Folder's m_children; which results into an error. Even if I include folder.h in common.cpp, I can't access folder's private members. Any ideas? :'

My foregoing answer was limited to showing you that what "makes it impossible for you to code the system like you had planned to" actually doesn't.

The problem you see now is that setting some folder f as the parent of some node n is not an independant operation on a node (file-or-folder). f can only validly become the parent of n if n simultaneously becomes one of the children of f. Thus in n.setParent(parent), at the same time as setting n.mParent == parent you wish to add n to parent->m_children; but m_children is inaccessible to the node n.

This problem is a heavy hint for your design. If setting-the-parent and adding-the-child must always occur together then they are effectively the same operation - set-parent-add-child - just invoked differently: from the parent or from the child. If there is a reason for Common to provide setParent(Folder *) then there is equally good reason for Folder to provide addChild(Common *), and they must both do the same thing.

Does this suggest that, say, static void Common::link(Folder * parent, Common * child) might better publically replace them both? Maybe so; but you started out with Common::setParent(Folder *), which was reasonable; so matching it with Folder::addChild(Common *) is reasonable too, and then we may get them to do the same thing by invoking each other.

Consider also, then, that since pCommon->setParent(pFolder) is
equivalent to pFolder->addChild(pCommon), you also need the means of removing a node from its parent; because before you can validly add a node to a parent you must remove it from its existing parent, if any. And this operation will very likely be a boon to client code; so Folder::removeChild(Common *) is also a natural addition to the Folder interface.

Folder::addChild(Common * pnode) and Folder::removeChild(Common * pnode) are the interfaces you are lacking to manage the private member Folder::m_children.

Next, consider that each of these methods is going to have to determine whether pnode is in fact a child of the folder: you must not add a child to a folder where it is already a child, and you cannot remove a child that isn't one. So Folder::find(Common * pnode) will also be useful - at least to the implementation (private), and plausibly also to client code (public): you can decide.

Then, consider that Folder::find(Common * pnode) calls for another method: bool Common::operator==(Common const & other). Let's just say that nodes are equal if they have the same name.

Common::clearParent() also springs to mind, but I will set that aside.

These ideas suffice for the following implementation, which is incomplete, suboptimal and impractical, but shows how you can join up the dots we have just identified so as to pass through the member-access obstacle that is still stopping you. It is impractical because it ignores the whole matter of the ownership of the dynamic objects that are assumed to be addressed by the Folder * and Common * arguments of its methods. You can deal with that yourself (and you may wish to investigate std::shared_ptr and std::unique_ptr, even if these are more advanced facilities than you are supposed to use in this project).

common.h

#ifndef COMMON_H
#define COMMON_H

#include <string>

#include <iostream>

class Folder;

class Common {
    std::string m_name;
    Folder* m_parent;

public:
    explicit Common(std::string const & name)
    : m_name(name),m_parent(nullptr){}
    virtual ~Common(){};
    virtual void open() { /*Whatever*/}
    virtual void draw() const {/*Whatever*/}
    virtual Folder* getParent() const { return m_parent; };
    virtual void setParent(Folder* parent);
    bool operator==(Common const & other) const {
        return m_name == other.m_name;
    }
    bool operator!=(Common const & other) const {
        return !(*this  == other);
    }
#if 1 // Testing
    std::string const & name() const {
        return m_name;
    }
    std::string parent() const;

    virtual void list() const {
        std::cout << name() << " (in " << parent() << ')' << std::endl ;
    }
#endif
};

#endif // EOF

folder.h

#ifndef FOLDER_H
#define FOLDER_H

#include "common.h"
#include <vector>

class Folder : public Common {
    std::vector<Common *> m_children;

    std::vector<Common *>::iterator find(Common const * child) {
        auto i = m_children.begin();
        for (   ;i != m_children.end() && **i != *child; ++i) {}
        return i;
    }

public:
    explicit Folder(std::string const & name)
    : Common(name){}
    virtual void open(){/*Whatever*/}
    virtual void draw() const {/*Whatever*/}
    void addChild(Common * child) {
        auto par = child->getParent();
        if (par && par != this) {
            par->removeChild(child);
        }
        if (find(child) == m_children.end()) {
            m_children.push_back(child);
            m_children.back()->setParent(this);
        }
    }
    void removeChild(Common const * child) {
        auto where = find(child);
        if (where != m_children.end()) {
            m_children.erase(where);
        }
    }
#if 1 // Testing
    void list() const {
        std::cout << name() << " {" << std::endl;
        for (Common const * child : m_children) {
            child->list();
        }
        std::cout << '}' << std::endl;
    }
#endif
};

#endif //EOF

file.h

#ifndef FILE_H
#define FILE_H

#include "common.h"

class File : public Common {
    // Whatever
public:
    explicit File(std::string const & name)
    : Common(name){}
    virtual void open(){/*Whatever*/};

};

#endif // EOF

common.cpp

#include "common.h"
#include "folder.h"

void Common::setParent(Folder* parent) {
    auto par = getParent();
    if (par && par != parent) {
        par->removeChild(this);
    }
    m_parent = parent;
    m_parent->addChild(this);
}

#if 1 // Testing
std::string Common::parent() const {
    return m_parent ? m_parent->name() : "<null>";
}
#endif

A test program:

#include "common.h"
#include "folder.h"
#include "file.h"

int main()
{
    Folder *fo0 = new Folder("folder0");
    File * fi0 = new File("file0");
    File * fi1 = new File("file1");
    fo0->addChild(fi0);
    fi1->setParent(fo0);
    fo0->addChild(fi0);  // Duplicate
    fi1->setParent(fo0); // Duplicate
    // There are now 2 files in folder fo0
    fo0->list();
    Folder *fo1 = new Folder("folder1");
    fo1->addChild(fi1);
    fi0->setParent(fo1);
    fo1->addChild(fi1); // Duplicate
    fi0->setParent(fo1); // Duplicate
    // There are now 0 files in folder fo0
    // There are now 2 files in folder fo1
    fo0->list();
    fo1->list();
    delete fo0;
    delete fo1;
    delete fi0;
    delete fi1;
    return 0;
}

他のヒント

You can´t put a folder inside common and make an inheritance of common in folder; it´s a cyclic redundancy error.

This system could be designed with two classes only: file and folder, but if you want to make a generalization, remove folder from common.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top