Я пытаюсь создать папку / файловую систему, похожую на те, которые используются в большинстве операционных систем.
В основном я понял, что должен использовать три класса; File
, Folder
и общий базовый класс. Давайте назовем это Common
ради творчества.
Вот как я думал, что заголовки для этих трех должны выглядеть так:
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;
};
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
};
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
};
Как видите, проблема в том, что мой базовый класс Common
должен быть в состоянии знать, что это подкласс Folder
, что является плохим поведением и не должно быть сделано (по крайней мере, по словам моего учителя).
Это делает невозможным для меня кодирование системы, как я планировал.
Как должна создаваться такая система?
Ваш существующий дизайн делает не требовать Common
«знать свой подкласс Folder
».
Это просто требует Common
заголовок, чтобы объявить, что есть такой класс
как 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;
};
Здесь нет цикла зависимости.
Если по какой-либо академической причине у вас должен быть базовый класс, который даже не
упомянуть любой подкласс, то вы можете сделать полиморфный базовый класс следующим образом:
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;
};
С этим дизайном однако setParent(Node* parent)
метод будет иметь
включить проверку во время выполнения, что Node *
аргумент parent
на самом деле
Folder *
используя, например,
Folder *pf = dynamic_cast<Folder *>(parent);
и в этом случае это также потребует не пустого типа возврата, с помощью которого
указать успех или неудачу. Это извилистый, а не просто сделать
декларация о class Folder
,
ПРОДОЛЖЕНИЕ ответить на дополнительный вопрос ОП.
Внутри общего
setParent()
Я должен позвонить папке m_children; что приводит к ошибке. Даже если я включаю folder.h в common.cpp, я не могу получить доступ к закрытым членам папки. Есть идеи? :»
Мой предыдущий ответ ограничивался показом вам, что то, что «делает невозможным для вас кодирование системы, как вы планировали», на самом деле не делает.
Проблема, которую вы видите сейчас, заключается в том, что установка какой-либо папки f
как родитель некоторых
узел n
не является независимой операцией на узле (файл или папка). f
может только
действительно стать родителем n
если n
одновременно становится одним из
дети f
, Таким образом, в n.setParent(parent)
одновременно с настройкой
n.mParent == parent
Вы хотите добавить n
в parent->m_children
;
но m_children
недоступен для узла n
,
Эта проблема является серьезным намеком на ваш дизайн. Если установка-родитель и добавление-ребенок
должны всегда происходить вместе, тогда они фактически являются одной и той же операцией — set-parent-add-child
— просто вызывается по-разному: от родителя или от ребенка. Если есть
причина для Common
предоставлять setParent(Folder *)
тогда там одинаково хорошо
причина для Folder
предоставлять addChild(Common *)
и они оба должны делать то же самое.
Это говорит о том, что, скажем, static void Common::link(Folder * parent, Common * child)
может лучше публично заменить их обоих? Может быть и так; но вы начали с
Common::setParent(Folder *)
,
что было разумно; так сопоставляя его с Folder::addChild(Common *)
разумно тоже,
и тогда мы можем заставить их делать то же самое, вызывая друг с другом.
Рассмотрим также, что pCommon->setParent(pFolder)
является
эквивалентно pFolder->addChild(pCommon)
Вам также нужны средства
удаление узел от его родителя; потому что прежде чем вы сможете правильно добавить
узел для родителя, вы должны удалить его из существующего родителя, если таковой имеется. А также
эта операция, скорее всего, будет благом для клиентского кода; так
Folder::removeChild(Common *)
также является естественным дополнением к
Folder
интерфейс.
Folder::addChild(Common * pnode)
а также Folder::removeChild(Common * pnode)
являются
интерфейсы, которых вам не хватает для управления приватным участником Folder::m_children
,
Далее, учтите, что каждый из этих методов должен
определить pnode
на самом деле является дочерним элементом папки: вы не должны добавлять
дочерний элемент в папку, где он уже является дочерним, и вы не можете удалить дочерний элемент, не являющийся таковым.
Так Folder::find(Common * pnode)
также будет полезно — по крайней мере, для реализации
(частный), и, вероятно, также к клиентскому коду (общедоступный): вы можете решить.
Затем учтите, что Folder::find(Common * pnode)
призывает для
другой метод: bool Common::operator==(Common const & other)
, Давайте просто скажем, что
узлы равны, если они имеют одинаковое имя.
Common::clearParent()
также приходит на ум, но я отложу это в сторону.
Этих идей достаточно для следующей реализации, которая
неполный, неоптимальный и непрактичный, но показывает, как вы можете объединить точки, которые мы имеем
только что определили, чтобы пройти через препятствие доступа члена, которое
все еще останавливаю тебя. Это нецелесообразно, потому что игнорирует
весь вопрос владения динамическими объектами, которые предполагаются
быть адресованным Folder *
а также Common *
аргументы его методов.
Вы можете справиться с этим самостоятельно (и вы можете захотеть исследовать
станд :: shared_ptr а также
станд :: unique_ptr, даже если
это больше
расширенные возможности, чем вы должны использовать в этом проекте).
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
Тестовая программа:
#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;
}
Вы не можете поместить папку в общую папку и создать наследование общей папки; это ошибка циклического избыточного кода.
Эта система может быть разработана только с двумя классами: файл и папка, но если вы хотите сделать обобщение, удалите папку из общего.