#include <string>
#include <memory>
#include <vector>
#include <iostream>
#include <functional>
class File;
class Directory;
class Visitor {
public:
virtual ~Visitor() {}
virtual void visit(File &) = 0;
virtual void visit(Directory &) = 0;
virtual void leave(Directory &) = 0;
};
class Entry {
public:
virtual ~Entry() {};
virtual std::string getName() const = 0;
virtual size_t getSize() = 0;
virtual void accept(Visitor &) = 0;
};
class File : public Entry {
std::string name;
size_t size;
public:
File(std::string name, size_t size) : name(name), size(size) {}
std::string getName() const override {return name;}
size_t getSize() override {return size;}
void accept(Visitor &v) override {v.visit(*this);}
};
class Directory : public Entry {
std::string name;
std::vector<std::shared_ptr<Entry>> es;
public:
Directory(std::string name) : name(name) {}
std::string getName() const override {return name;}
size_t getSize() override {
size_t sum = 0;
for (auto &e : es) sum += e->getSize();
return sum;
}
void accept(Visitor &v) override {
v.visit(*this);
for (auto &e : es) e->accept(v);
v.leave(*this);
}
void add(std::shared_ptr<Entry> entry) {es.push_back(entry);}
};
// こっから↑は安定したクラス階層を想定
//////////////////////////////////////////////////////////////////
// こっから↓はVisitor派生クラスを作ることで「新しいオペレーションを簡単に追加できる」とのこと。
class SizeVisitor : public Visitor {
size_t size;
public:
SizeVisitor() : size(0) {};
void visit(File &file) {size += file.getSize();}
void visit(Directory &) {}
void leave(Directory &) {}
size_t getTotalSize() {return size;}
};
class ListVisitor : public Visitor {
std::string cd;
public:
ListVisitor() : cd("") {}
void visit(File &file) {
std::cout << cd << "/" << file.getName() << " (" << file.getSize() << ")" << std::endl;
}
void visit(Directory &dir) {
std::cout << cd << "/" << dir.getName() << " (" << dir.getSize() << ")" << std::endl;
cd += "/" + dir.getName();
}
void leave(Directory &dir) {
cd.resize(cd.length() - 1 - dir.getName().length());
}
};
class FunctionalVisitor : public Visitor {
std::function<void(File &)> vf;
std::function<void(Directory &)> vd, ld;
public:
FunctionalVisitor(
std::function<void(File &)> vf, std::function<void(Directory &)> vd, std::function<void(Directory &)> ld
) : vf(vf), vd(vd), ld(ld) {}
void visit(File &file) {vf(file);}
void visit(Directory &dir) {vd(dir);}
void leave(Directory &dir) {ld(dir);}
};
int main() {
auto a = std::make_shared<Directory>("a");
auto b = std::make_shared<File>("b.txt", 100);
auto c = std::make_shared<File>("c.txt", 200);
auto d = std::make_shared<Directory>("d");
auto e = std::make_shared<File>("e.txt", 300);
a->add(b);
a->add(c);
a->add(d);
d->add(e);
SizeVisitor sv;
a->accept(sv);
std::cout << "total size: " << sv.getTotalSize() << std::endl;
ListVisitor lv;
std::cout << "\nlist: " << std::endl;
a->accept(lv);
std::vector<std::string> dirs;
FunctionalVisitor fv(
[&dirs](File &file) {
if (file.getName().find(".txt") != std::string::npos) {
for (auto &d : dirs) std::cout << "/" << d;
std::cout << "/" << file.getName() << std::endl;
}
}
, [&dirs](Directory &dir) {dirs.push_back(dir.getName());}
, [&dirs](Directory &dir) {dirs.pop_back();}
);
std::cout << "\n.txt: " << std::endl;
a->accept(fv);
return 0;
}
/*
・https://i...content-available-to-author-only...e.com/oYzkxh を元に若干の整理を行った
・他の人と同様shared_ptrを削除
値で持てるところは単に値で持つほうがC++っぽいと思う
ただ「Entry を値で持つのはいやだなあ」とのことなので部分的に残してる
Javaの参照型変数をshared_ptrに置き換えようとして困るのは
size_t File::accept(std::shared_ptr<Visitor> v) { return v->visit(std::make_shared<File>(this)); }
ここがJavaだと単にvisit(this)で済むからスッキリするんだけど
しかもこれmake_shared(this)だと多重開放するよね??
・オリジナルのイテレータ実装は削除
Directory クラスがイテレータを公開したくなるのをぐっと堪えて我慢
・インタフェースを整理
そもそもVisitorパターンってのはGoF本構造の段落にある
Visitor, Element, ConcreteElementクラスに変更がなさげでクラス階層の安定が前提で
ConcreteVisitorを追加したり変更したりすることでなんとかしていくパターン。
だから、それを念頭に依存関係、呼び出し関係を整理していく
・抽象クラスをより抽象的に
Entry::accept, Visitor::visitは抽象的なままにしておきたい
この先どんなConcreteVisitorが来ても自然に受け入れられるように真っ白にしておきたいから
size_tを返すようなことはなんとなくしたくない、色をつけたくない。
GoF本のサンプルコードもvoidを返すようになってるからそれに従った
・依存関係の正常化
ConcreteElementがConcreteVisitorを知っているようなのは気持ちが悪い
void Directory::treePrint() {auto visitor = std::make_shared<ListVisitor>();}
こういうところが循環すると見通しが悪くなってくるし保守性も下がると思う
ConcreteElementとConcreteVisitorを組合わせて使うのはクライアントだけでいい
ここでクライアントとはクラス群を使うユーザ、使う文脈みたいな意味、たとえばこのmain文を書く人。
SizeVisitorを知っているクライアントは、具象クラス固有の関数SizeVisitor::getTotalSize()も平然と使う。
このへんの依存関係の整理は徹底したいところ
・visit()側で要素ケアしてたのをaccept()側に移動
これはGoF本実装の段、「CompositeElementクラスはAcceptオペレーションを次のように実装する」以下のテストコードに準拠。
void CompositeElement::Accept(Visitor &v) {
ListIterator<Element *> i(_children);
for (i.First(); !i.IsDone(); i.Next()) {i.CurrentItem()->Accept(v);
v.VisitCompositeElement(this);
}
これをこっちでするからこそDirectoryクラスがイテレータを外部に公開しなくて済んでる
・Visitor::leave()を追加
これはVisitorパターンにとって本質的じゃない
勝手にこちらの実装の都合で用意しただけなんで気にしないで欲しい
・オブジェクト構造を走査する責任をどのオブジェクトに持たせるのか
これは「GoF本実装の段2.」に20行以上説明があるので興味があったら読んでみてほしい
「visitorの中に走査のアルゴリズムを入れることもできる。しかし…」とある
ちなみに今回の実装ではConcreteElement::Accept側で走査を行うようにした
*/