Visitorパターン
処理を行うクラスとデータ構造を持つクラスを分離する
Visitor とは、英語で「訪問者」を意味します。Visitor パターンでは、「処理」を訪問者である Visitor クラスに記述することで、処理の追加を簡単にします。
あるオブジェクトに対する処理を別のオブジェクトに委ねるわけです。この際、相手のオブジェクトに、thisキーワードを使って、自分自身をすべて引き渡してしまいます。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくするのです。

例えば、家の「水道工事」を行ってもらう場合、あなたは、「水道工事業者」を家に呼んで、「よろしくお願いします。」と言って、後は全てお任せしますよね。同様に、「庭の手入れ」を行ってもらう場合は、「庭師」を家に呼んで、全てお任せしてしまうでしょう。そのほかにも、電気工事業者を呼ぶことも、リフォーム業者を呼ぶこともあるでしょう。これらの訪問者に対して、あなたは、「では、よろしく」と言って、ほとんどの作業をお任せするはずです。
お任せの仕方に多少の違いがあるかもしれませんが、最終的には、全てを業者にお任せすることになると思います。もし、新しいサービスを提供する業者が現れたときにも、各家庭は、なんら態度を変える必要が無く、その業者を呼んで、「よろしくお願いします。」というだけで、その新しいサービスを受けることができます。
- accept() メソッドは以下のような呼び出しになります。
- element.accept(visitor)
- visit メソッドは以下のような呼び出しになります。
- visitor.visit(element)
この2つを見るとちょうど正反対の関係になります。なぜこのような複雑なメソッド呼び出しをしなけらばならないのでしょうか。
それは、処理(visitor)をデータ構造(element)から分離するためです。同じデータ構造に対してまったく違う処理をする場合にも visitor の変更だけですみます。
ただし、各 element に処理を持たせず visitor に集中させるというのは、気を付けないとカプセル化を破壊することにもなります。visitor は element から必要な情報を取得して働きますが、必要な情報を得られないとうまく働くことができません。その反面、公開すべきではない情報までを公開してしまうと、将来データ構造を変更することが難しくもなるからです。
また、対象となる element が増えると、各 visitor がそれを扱えるようにメソッドを追加する必要がでてくる点にも注意が必要です。
例題
dir1
├ file1
├ dir2
│ ├ file2
│ └ file3
└ file4
ファイルシステムは、ディレクトリとファイルの階層構造でできています。指定されたディレクトリの中のファイルとそのサイズを表示するクラスを作成しなさい。もちろん、さらに下層のディレクトリの中のファイルも表示します。
実行結果
dir1\dir2\file2 (20MB)
dir1\dir2\file3 (30MB)
dir1\file4 (40MB)
|
Visitorパターンを使用しない例 IEntry.java public interface IEntry {
public void dir(String path);
}
Directory.java public class Directory implements IEntry {
private List<IEntry> list = null;
private String name = null;
public Directory(String name) {
this.name = name;
list = new ArrayList<IEntry>();
}
public void add(IEntry entry) {
list.add(entry);
}
public void dir(String path) {
path += name + "\\";
Iterator<IEntry> it = list.iterator();
while (it.hasNext()) {
IEntry entry = it.next();
entry.dir(path);
}
}
}
File.java public class File implements IEntry {
private String name = null;
private int size = 0;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public void dir(String path) {
System.out.println(path + name + " (" + size + "MB)");
}
}
Main.java public class Main {
public static void main(String[] args) {
File file1 = new File("file1", 10);
File file2 = new File("file2", 20);
File file3 = new File("file3", 30);
File file4 = new File("file4", 40);
Directory dir1 = new Directory("dir1");
dir1.add(file1);
Directory dir2 = new Directory("dir2");
dir2.add(file2);
dir2.add(file3);
dir1.add(dir2);
dir1.add(file4);
dir1.dir("");
}
}
|
Visitorパターンを使用した例 IEntry.java public interface IEntry {
void accept(IVisitor v);
String getName();
}
Directory.java public class Directory implements IEntry {
private List<IEntry> list = null;
private String name = null;
public Directory(String name) {
this.name = name;
list = new ArrayList<IEntry>();
}
public String getName() {
return name;
}
public IEntry add(IEntry entry) {
list.add(entry);
return this;
}
public Iterator<IEntry> iterator() {
return list.iterator();
}
public void accept(IVisitor v) {
v.visit(this);
}
}
File.java public class File implements IEntry {
private String name = null;
private int size = 0;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
public void accept(IVisitor v) {
v.visit(this);
}
}
IVisitor.java public interface IVisitor {
void visit(File file);
void visit(Directory directory);
}
ListVisitor.java public class ListVisitor implements IVisitor {
private String currentDir = "";
public void visit(File file) {
System.out.println(currentDir + file.getName() + " (" + file.getSize() + "MB)");
}
public void visit(Directory directory) {
String saveDir = currentDir;
currentDir += directory.getName() + "\\";
Iterator<IEntry> it = directory.iterator();
while (it.hasNext()) {
IEntry entry = (IEntry) it.next();
entry.accept(this);
}
currentDir = saveDir;
}
}
Main.java public class Main {
public static void main(String[] args) {
File file1 = new File("file1", 10);
File file2 = new File("file2", 20);
File file3 = new File("file3", 30);
File file4 = new File("file4", 40);
Directory dir1 = new Directory("dir1");
dir1.add(file1);
Directory dir2 = new Directory("dir2");
dir2.add(file2);
dir2.add(file3);
dir1.add(dir2);
dir1.add(file4);
dir1.accept(new ListVisitor());
}
}
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述したDirectory クラス、File クラス共に dir() メソッドの中にどう表示するか(Directory クラスはフォルダ名を\でつなげ、File クラスは括弧の中にファイルサイズを表示する)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Directory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visit(Directory) が呼び出されます。visit(Directory) からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visit(Directory) が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visit(File) が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、thisキーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |
|
Visitorパターンを使用しない例 ientry.h #include <string>
class IEntry
{
public:
IEntry(void);
virtual ~IEntry(void);
virtual void dir(std::string) = 0;
}; ientry.cpp #include "ientry.h"
IEntry::IEntry(void) {}
IEntry::~IEntry(void) {}
directory.h #include <list>
#include "ientry.h"
class Directory : public IEntry
{
private:
std::list<IEntry*> entryList;
std::string name;
public:
Directory(void);
Directory(std::string);
virtual ~Directory(void);
void add(IEntry*);
void dir(std::string);
}; directory.cpp #include "directory.h"
using namespace std;
Directory::Directory(void) {}
Directory::Directory(string name) : name(name) {}
Directory::~Directory(void) {}
void Directory::add(IEntry* e) {
entryList.push_back(e);
}
void Directory::dir(string path) {
path += name + "\\";
list<IEntry*>::iterator it = entryList.begin();
while(it != entryList.end()){
IEntry* e = *it++;
ite->dir(path);
}
}
file.h #include "ientry.h"
class File : public IEntry
{
private:
std::string name;
int size;
public:
File(void);
File(std::string, int);
virtual ~File(void);
void dir(std::string);
}; file.cpp #include <iostream>
using namespace std;
#include "file.h"
File::File(void) {}
File::File(string n, int s) : name(n), size(s) {}
File::~File(void) {}
void File::dir(string path) {
cout << path << name << " (" << size << "MB)" << endl;
}
main.cpp #include <iostream>
using namespace std;
#include "directory.h"
#include "file.h"
int main() {
File file1("file1", 10);
File file2("file2", 20);
File file3("file3", 30);
File file4("file4", 40);
Directory dir1("dir1");
dir1.add(&file1);
Directory dir2("dir2");
dir2.add(&file2);
dir2.add(&file3);
dir1.add(&dir2);
dir1.add(&file4);
dir1.dir("");
return 0;
}
|
Visitorパターンを使用した例 ientry.h #include string
class IEntry
{
public:
IEntry(void);
virtual ~IEntry(void);
virtual std::string getName(void) = 0;
virtual void accept(IVisitor*) = 0;
}; ientry.cpp #include "ientry.h"
IEntry::IEntry(void) {}
IEntry::~IEntry(void) {}
directory.h #include <list>
#include "ientry.h"
#include "ivisitor.h"
class Directory : public IEntry
{
private:
std::list<IEntry*> entryList;
std::string name;
public:
Directory(void);
Directory(std::string);
virtual ~Directory(void);
void add(IEntry*);
std::string getName(void);
void accept(IVisitor*);
std::list<IEntry*>& getList(void);
}; directory.cpp #include "dir.h"
using namespace std;
Directory::Directory(void) {}
Directory::Directory(string n) : name(name) {}
Directory::~Directory(void) {}
void Directory::add(IEntry* entry) {
entryList.push_back(entry);
}
void Directory::accept(IVisitor* v) { v->visit(this); }
list<IEntry*>& Directory::getList(void) { return entryList; }
}
file.h #include "ientry.h"
#include "ivisitor.h"
class File : public IEntry
{
private:
std::string name;
int size;
public:
File(void);
File(std::string, int);
virtual ~File(void);
std::string getName(void);
int getSize(void);
void accept(IVisitor*);
}; file.cpp #include <sstream>
using namespace std;
#include "file.h"
File::File(void) {}
File::File(string n, int s) : name(n), size(s) {}
File::~File(void) {}
string File::getName(void) { return name; }
int File::getSize(void) { return size; }
void File::accept(IVisitor* v) { v->visit(this); }
ivisitor.h class Directory;
class File;
class IVisitor
{
public:
IVisitor(void);
IVisitor ~IVisitor(void);
virtual void visit(File*) = 0;
virtual void visit(Directory*) = 0;
}; ivisitor.cpp #include "ivisitor.h"
IVisitor::IVisitor(void) {}
IVisitor::~IVisitor(void) {}
listVisitor.h #include "directory.h"
#include "file.h"
#include "ivisitor.h"
class ListVisitor : public IVisitor
{
private:
std::string currentDir;
public:
ListVisitor(void);
virtual ~ListVisitor(void);
void visit(File*);
void visit(Directory*);
};
listVisitor.cpp #include <list>
#include <string>
#include <iostream>
using namespace std;
#include <sstream>
#include "listVisitor.h"
ListVisitor::ListVisitor(void) : currentDir("") {}
ListVisitor::~ListVisitor(void) {}
void ListVisitor::visit(File* file) {
ostringstream s;
s << file->getSize();
cout << currentDir << file->getName() + " (" + s.str() + "MB)" << endl;
}
void ListVisitor::visit(Directory* directory) {
string saveDir = currentDir;
currentDir += directory->getName() + "\\";
list<IEntry*>& lst = directory->getList();
list<IEntry*>::iterator it = lst.begin();
while(it != lst.end()) {
IEntry* entry = *it++;
entry->accept(this);
}
currentDir = saveDir;
}
main.cpp #include "listVisitor.h"
#include "directory.h"
#include "file.h"
int main() {
File file1("file1", 10);
File file2("file2", 20);
File file3("file3", 30);
File file4("file4", 40);
Directory dir1("dir1");
dir1.add(&file1);
Directory dir2("dir2");
dir2.add(&file2);
dir2.add(&file3);
dir1.add(&dir2);
dir1.add(&file4);
ListVisitor v;
dir1.accept(&v);
return 0;
}
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述した Directory クラス、File クラス共に dir() メソッドの中にどう表示するか(フォルダ名を\でつなげる)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Drectory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visit(Directory) が呼び出されます。visit(Directory) からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visit(Directory) が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visit(File) が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、this キーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |
|
Visitorパターンを使用しない例 IEntry.cs interface IEntry
{
void Dir(string path);
}
Directory.cs class Directory : IEntry
{
private ArrayList list;
private string name;
public Directory(string name)
{
this.name = name;
list = new ArrayList();
}
public void Add(IEntry entry)
{
list.Add(entry);
}
public void Dir(string path)
{
path += name + "/";
foreach (IEntry entry in list)
{
entry.Dir(path);
}
}
}
File.cs class File : IEntry
{
private string name;
private int size;
public File(string name, int size)
{
this.name = name;
this.size = size;
}
public void Dir(string path)
{
Console.WriteLine(path + name + "(" + size + "MB)");
}
}
Program.cs class Program
{
static void Main(string[] args)
{
Directory dir1 = new Directory("dir1");
dir1.Add(new File("file1", 10));
Directory dir2 = new Directory("dir2");
dir1.Add(dir2);
dir2.Add(new File("file2", 20));
dir2.Add(new File("file3", 30));
dir1.Add(new File("file4", 40));
dir1.Dir("");
}
}
|
Visitorパターンを使用した例 IEntry.cs interface IEntry
{
void Accept(IVisitor v);
string GetName();
}
Directory.cs class Directory : IEntry
{
private ArrayList list;
private string name;
public Directory(string name)
{
this.name = name;
list = new ArrayList();
}
public IEntry Add(IEntry entry)
{
list.Add(entry);
return this;
}
public ArrayList GetList()
{
return list;
}
public string GetName()
{
return name;
}
public void Accept(IVisitor v)
{
v.Visit(this);
}
}
File.cs class File : IEntry
{
private string name;
private int size;
public File(string name, int size)
{
this.name = name;
this.size = size;
}
public string GetName()
{
return name;
}
public int GetSize()
{
return size;
}
public void Accept(IVisitor v)
{
v.Visit(this);
}
}
IVisitor.cs interface IVisitor
{
void Visit(File file);
void Visit(Directory directory);
}
ListVisitor.cs class ListVisitor : IVisitor
{
private string currentDir = "";
public void Visit(File file)
{
Console.WriteLine(currentDir + file.GetName()
+ "(" + file.GetSize() + "MB)");
}
public void Visit(Directory directory)
{
string saveDir = currentDir;
currentDir += directory.GetName() + '/';
foreach(IEntry entry in directory.GetList())
{
entry.Accept(this);
}
currentDir = saveDir;
}
}
Program.cs class Program
{
static void Main(string[] args)
{
Directory dir1 = new Directory("dir1");
dir1.Add(new File("file1", 10));
Directory dir2 = new Directory("dir2");
dir1.Add(dir2);
dir2.Add(new File("file2", 20));
dir2.Add(new File("file3", 30));
dir1.Add(new File("file4", 40));
dir1.Accept(new ListVisitor());
}
}
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述したDirectory クラス、File クラス共に dir() メソッドの中にどう表示するか(Directory クラスはフォルダ名を\でつなげ、File クラスは括弧の中にファイルサイズを表示する)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Directory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visit(Directory) が呼び出されます。visit(Directory) からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visit(Directory) が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visit(File) が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、this キーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |
|
Visitorパターンを使用しない例 IEntry.vb Public Interface IEntry
Sub Dir(ByVal path As String)
End Interface
Directory.vb Public Class Directory
Implements IEntry
Private list As ArrayList
Private name As String
Public Sub New(ByVal name As String)
Me.name = name
list = New ArrayList()
End Sub
Public Sub Add(ByVal entry As IEntry)
list.Add(entry)
End Sub
Public Sub Dir(ByVal path As String) Implements IEntry.Dir
path &= name & "\"
For Each entry As IEntry In list
entry.Dir(path)
Next
End Sub
End Class
File.vb Public Class File
Implements IEntry
Private name As String
Private size As Integer
Public Sub New(ByVal name As String, ByVal size As Integer)
Me.name = name
Me.size = size
End Sub
Public Sub Dir(ByVal path As String) Implements IEntry.Dir
Console.WriteLine(path & name & "(" & size & "MB)")
End Sub
End Class
Program.vb Module Main
Sub Main()
Dim dir1 As Directory = New Directory("dir1")
dir1.Add(New File("file1", 10))
Dim dir2 As Directory = New Directory("dir2")
dir1.Add(dir2)
dir2.Add(New File("file2", 20))
dir2.Add(New File("file3", 30))
dir1.Add(New File("file4", 40))
dir1.Dir("")
End Sub
End Module
|
Visitorパターンを使用した例 IEntry.vb Public Interface IEntry
Sub Accept(ByVal v As IVisitor)
Function GetName() As String
End Interface
Directory.vb Public Class Directory
Implements IEntry
Private list As ArrayList
Private name As String
Public Sub New(ByVal name As String)
Me.name = name
list = New ArrayList()
End Sub
Public Function Add(ByVal entry As IEntry) As IEntry
list.Add(entry)
Return Me
End Function
Public Function GetList() As ArrayList
Return list
End Function
Public Function GetName() As String Implements IEntry.GetName
Return name
End Function
Public Sub Accept(ByVal v As IVisitor) Implements IEntry.Accept
v.Visit(Me)
End Sub
End Class
File.vb Public Class File
Implements IEntry
Private name As String
Private size As Integer
Public Sub New(ByVal name As String, ByVal size As Integer)
Me.name = name
Me.size = size
End Sub
Public Function GetName() As String Implements IEntry.GetName
Return name
End Function
Public Function GetSize() As Integer
Return size
End Function
Public Sub Accept(ByVal v As IVisitor) Implements IEntry.Accept
v.visit(Me)
End Sub
End Class
IVisitor.vb Public Interface IVisitor
Sub Visit(ByVal file As File)
Sub Visit(ByVal directory As Directory)
End Interface
ListVisitor.vb Public Class ListVisitor
Implements IVisitor
Private currentDir As String = ""
Public Sub Visit(ByVal file As File) Implements IVisitor.Visit
Console.WriteLine(currentDir & "(" & file.GetSize() & "MB)")
End Sub
Public Sub Visit(ByVal directory As Directory) Implements IVisitor.Visit
Dim saveDir As String = currentDir
currentDir &= directory.GetName() & "\"
For Each entry As IEntry In directory.GetList()
entry.Accept(Me)
Next
currentDir = saveDir
End Sub
End Class
Program.vb Module Main
Sub Main()
Dim dir1 As Directory = New Directory("dir1")
dir1.Add(New File("file1", 10))
Dim dir2 As Directory = New Directory("dir2")
dir1.Add(dir2)
dir2.Add(New File("file2", 20))
dir2.Add(New File("file3", 30))
dir1.Add(New File("file4", 40))
dir1.Accept(New ListVisitor())
End Sub
End Module
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述したDirectory クラス、File クラス共に dir() メソッドの中にどう表示するか(Directory クラスはフォルダ名を\でつなげ、File クラスは括弧の中にファイルサイズを表示する)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Directory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visit(Directory) が呼び出されます。visit(Directory) からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visit(Directory) が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visit(File) が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、Me キーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |
|
Visitorパターンを使用しない例 IEntry.js module.exports = class IEntry {
dir() { console.log("dir メソッドを定義してください。"); }
}
Directory.js const IEntry = require("./IEntry.js");
module.exports = class Directory extends IEntry {
constructor(name) {
super();
this.name = name;
this.list = new Array();
}
add(entry) {
if (entry instanceof IEntry)
this.list.push(entry);
}
dir(path) {
path += this.name + "\\";
this.path = path;
this.list.forEach(
function(entry) { entry.dir(this.path); },
this);
}
}
File.js const IEntry = require("./IEntry.js");
module.exports = class File extends IEntry {
constructor(name, size) {
super();
this.name = name;
this.size = size;
}
dir(path) {
process.stdout.write(path + this.name + " (" + this.size + "MB)\n");
}
}
Main.js const File = require("./File.js");
const Directory = require("./Directory.js");
let file1 = new File("file1", 10);
let file2 = new File("file2", 20);
let file3 = new File("file3", 30);
let file4 = new File("file4", 40);
let dir1 = new Directory("dir1");
dir1.add(file1);
let dir2 = new Directory("dir2");
dir2.add(file2);
dir2.add(file3);
dir1.add(dir2);
dir1.add(file4);
dir1.dir("");
|
Visitorパターンを使用した例 IEntry.js module.exports = class IEntry {
constructor() {}
getName() {
console.log("getName メソッドを定義してください。");
}
accept() {
console.log("accept メソッドを定義してください。");
}
}
Directory.js const IEntry = require("./IEntry.js");
module.exports = class Directory extends IEntry {
constructor(name) {
super();
this.name = name;
this.list = new Array();
}
getName() {
return this.name;
}
add(entry) {
if (entry instanceof IEntry)
this.list.push(entry);
}
getList() {
return this.list;
}
accept(v) {
v.visitDirectory(this);
}
}
File.js const IEntry = require("./IEntry.js");
module.exports = class File extends IEntry {
constructor(name, size) {
super();
this.name = name;
this.size = size;
}
getName() {
return this.name;
}
getSize() {
return this.size;
}
accept(v) {
v.visitFile(this);
}
}
ListVisitor.js module.exports = class ListVisitor {
constructor() {
this.currentDir = "";
}
visitFile(file) {
process.stdout.write(this.currentDir + file.getName()
+ " (" + file.getSize() + "MB)\n");
}
visitDirectory(directory) {
let saveDir = this.currentDir;
this.currentDir += directory.getName() + "\\";
let list = directory.getList();
list.forEach(
function(entry) { entry.accept(this); },
this);
this.currentDir = saveDir;
}
}
Main.js const File = require("./File.js");
const Directory = require("./Directory.js");
const ListVisitor = require("./ListVisitor.js");
let file1 = new File("file1", 10);
let file2 = new File("file2", 20);
let file3 = new File("file3", 30);
let file4 = new File("file4", 40);
let dir1 = new Directory("dir1");
dir1.add(file1);
let dir2 = new Directory("dir2");
dir2.add(file2);
dir2.add(file3);
dir1.add(dir2);
dir1.add(file4);
dir1.accept(new ListVisitor());
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述したDirectory クラス、File クラス共に dir() メソッドの中にどう表示するか(Directory クラスはフォルダ名を\でつなげ、File クラスは括弧の中にファイルサイズを表示する)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Directory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visitDirectory が呼び出されます。visitDirectory からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visitDirectory が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visitFile が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、this キーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |
|
Visitorパターンを使用しない例 IEntry.pm package IEntry {
sub dir { print "dir メソッドを定義してください。"; }
}
Directory.pm package Directory {
use base qw(IEntry);
sub new {
my ($class, $name) = @_;
my $this = { name => $name, list => () };
return bless $this, $class;
}
sub add {
my ($this, $entry) = @_;
if ($entry->isa("IEntry")) {
push @{$this->{list}}, $entry;
}
}
sub dir {
my ($this, $path) = @_;
$path .= $this->{name} . "\\";
$this->{path} = $path;
foreach my $entry (@{$this->{list}}) {
$entry->dir($this->{path});
}
}
}
1;
File.pm package File {
use base qw(IEntry);
sub new {
my ($this, $name, $size) = @_;
my $this = { name => $name, size => $size };
return bless $this, $class;
}
sub dir {
my ($this, $path) = @_;
print $path . $this->{name} . " ($this->{size}MB)\n");
}
}
1;
Main.pl use lib qw(./);
use File;
use Directory;
my $file1 = new File("file1", 10);
my $file2 = new File("file2", 20);
my $file3 = new File("file3", 30);
my $file4 = new File("file4", 40);
my $dir1 = new Directory("dir1");
$dir1->add($file1);
my $dir2 = new Directory("dir2");
$dir2->add($file2);
$dir2->add($file3);
$dir1->add($dir2);
$dir1->add($file4);
$dir1->dir("");
|
Visitorパターンを使用した例 IEntry.pl package IEntry {
sub getName {
print "getName メソッドを定義してください。";
}
sub accept() {
print "accept メソッドを定義してください。";
}
}
1;
Directory.pm package Directory {
use base qw(IEntry);
sub new {
my ($class, $name) = @_;
my $this = { name => $name, list => () };
return bless $this, $class;
}
sub getName {
my ($this) = @_;
return $this->{name};
}
sub add {
my ($this, $entry) = @_;
if ($entry->isa("IEntry")) {
push @{$this->{list}}, $entry;
}
}
sub getList {
my ($this) = @_;
return $this->{list};
}
sub accept {
my ($this, $v) = @_;
$v->visitDirectory($this);
}
}
1;
File.pm package File {
use base qw(IEntry);
sub new {
my ($class, $name, $size) = @_;
my $this = { name => $name, size => $size };
return bless $this, $class;
}
sub getName {
my ($this) = @_;
return $this->{name};
}
sub getSize {
my ($this) = @_;
return $this->{size};
}
sub accept {
my ($this, $v) = @_;
$v->visitFile($this);
}
}
1;
ListVisitor.pm package ListVisitor {
sub new {
my ($class) = @_;
my $this = { currentDir => "" };
return bless $this, $class;
}
sub visitFile {
my ($this, $file) = @_;
print $this->{currentDir} . $file->getName() . " (" . $file->getSize() . "MB)\n";
}
sub visitDirectory {
my ($this, $directory) = @_;
my $saveDir = $this->{currentDir};
$this->{currentDir} .= $directory->getName() . "\\";
my $list = $directory->getList();
foreach my $entry (@{$list}) {
$entry->accept($this);
}
$this->{currentDir} = $saveDir;
}
}
1;
Main.pl use lib qw(./);
use File;
use Directory;
use ListVisitor;
my $file1 = new File("file1", 10);
my $file2 = new File("file2", 20);
my $file3 = new File("file3", 30);
my $file4 = new File("file4", 40);
my $dir1 = new Directory("dir1");
$dir1->add($file1);
my $dir2 = new Directory("dir2");
$dir2->add($file2);
$dir2->add($file3);
$dir1->add($dir2);
$dir1->add($file4);
$dir1->accept(new ListVisitor());
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述したDirectory クラス、File クラス共に dir() メソッドの中にどう表示するか(Directory クラスはフォルダ名を\でつなげ、File クラスは括弧の中にファイルサイズを表示する)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Directory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visitDirectory が呼び出されます。visitDirectory からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visitDirectory が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visitFile が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、this キーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |
|
Visitorパターンを使用しない例 IEntry.rb class IEntry
def dir()
puts "dir メソッドを定義してください。"
end
end
Directory.rb require './IEntry'
class Directory < IEntry
def initialize(name)
super()
@name = name
@list = Array.new()
end
def add(entry) {
if entry.is_a?(IEntry) then
@list.push(entry)
end
end
def dir(path)
path += @name + "\\"
@path = path
@list.each do |entry|
entry.dir(@path)
end
end
end
File.rb require './IEntry'
class Filex < IEntry
def initialize(name, size)
super()
@name = name
@size = size
end
def dir(path)
puts "#{path}#{@name}(#{@size.to_s}MB)"
end
end
Main.rb require './File'
require './Directory'
file1 = Filex.new("file1", 10)
file2 = Filex.new("file2", 20)
file3 = Filex.new("file3", 30)
file4 = Filex.new("file4", 40)
dir1 = Directory.new("dir1")
dir1.add(file1)
dir2 = Directory.new("dir2")
dir2.add(file2)
dir2.add(file3)
dir1.add(dir2)
dir1.add(file4)
dir1.dir("")
|
Visitorパターンを使用した例 IEntry.rb class IEntry
def getName()
puts "getName メソッドを定義してください。"
end
def accept()
puts "accept メソッドを定義してください。"
end
end
Directory.rb require './IEntry'
class Directory < IEntry
def initialize(name)
super()
@name = name
@list = Array.new()
end
def getName()
return @name
end
def add(entry) {
if entry.is_a?(IEntry) then
@list.push(entry)
end
end
def getList()
return @list
end
def accept(v)
v.visitDirectory(self)
end
end
File.rb require './IEntry'
class Filex < IEntry
def initialize(name, size)
super()
@name = name
@size = size
end
def getName()
return @name
end
def getSize()
return @size
end
def accept(v)
v.visitDirectory(self)
end
end
ListVisitor.rb class ListVisitor
def initialize()
@currentDir = ""
end
def visitFile(file)
puts @currentDir + file.getName() + " (" + file.getSize().to_s + "MB)\n"
end
def visitDirectory(directory)
saveDir = @currentDir;
@currentDir += directory.getName() + "\\"
list = directory.getList()
list.each do |entry|
entry.accept(self)
end
@currentDir = saveDir;
end
end
Main.rb require './File'
require './Directory'
file1 = Filex.new("file1", 10)
file2 = Filex.new("file2", 20)
file3 = Filex.new("file3", 30)
file4 = Filex.new("file4", 40)
dir1 = Directory.new("dir1")
dir1.add(file1)
dir2 = Directory.new("dir2")
dir2.add(file2)
dir2.add(file3)
dir1.add(dir2)
dir1.add(file4)
dir1.accept(ListVisitor.new())
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述したDirectory クラス、File クラス共に dir() メソッドの中にどう表示するか(Directory クラスはフォルダ名を\でつなげ、File クラスは括弧の中にファイルサイズを表示する)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Directory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visitDirectory が呼び出されます。visitDirectory からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visitDirectory が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visitFile が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、this キーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |
この例では、IEntry インタフェースを実装した Directoryクラスで addメソッドを定義しています。実際、addメソッドを使えるのが Directoryクラスだけだからです。しかし、 addメソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entryに実装し、例外を発生させる。
- addメソッドを Entryクラスに実装し、例外を発生させます。addメソッドを実際に使える Directoryクラスでは、 Entryクラスのaddメソッドをオーバーライドして、意味のある実装に置き換えます。Filexクラスでは、 Entryクラスのaddメソッドを継承しているのでaddは可能ですが、例外が投げられます。
- 抽象クラス Entryに実装し、何も行わない。
- addメソッドを Entryクラスに実装するが、何も行わないようにすることもできます。そうすると、Filexクラスからのaddは例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entryで宣言はするが、実装はしない。
- addメソッドを Entryクラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directoryクラスにだけ記述する。
- この例題がそうです。addメソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要なDirectoryクラスにだけ入れるというやり方です。ただし、このやり方だと、Directoryのインスタンスが Entry型の変数に入っているときは、 Directory型にキャストしないとaddメソッドが使えないことになります。
|
Visitorパターンを使用しない例 IEntry.py from abc import ABCMeta, abstractmethod
class IEntry(metaclass=ABCMeta):
@abstractmethod
def dir(self, path):
pass
Directory.py from IEntry import IEntry
class Directory(IEntry):
def __init__(self, name):
self.name = name
self.list = []
def add(self, entry):
if isinstance(entry, IEntry):
self.list.append(entry)
def dir(self, path):
path += self.name + "\\"
self.path = path
for entry in self.list:
entry.dir(self.path)
File.py from IEntry import IEntry
class File(IEntry):
def __init__(self, name, size):
self.name = name
self.size = size
def dir(self, path):
print(f"{path}{self.name} ({self.size}MB)")
Main.py from File import File
from Directory import Directory
file1 = File("file1", 10)
file2 = File("file2", 20)
file3 = File("file3", 30)
file4 = File("file4", 40)
dir1 = Directory("dir1")
dir1.add(file1)
dir2 = Directory("dir2")
dir2.add(file2)
dir2.add(file3)
dir1.add(dir2)
dir1.add(file4)
dir1.dir("")
|
Visitorパターンを使用した例 IEntry.js from abc import ABCMeta, abstractmethod
class IEntry(metaclass=ABCMeta):
@abstractmethod
def getName(self):
pass
@abstractmethod
def accept(self, v):
pass
Directory.py from IEntry import IEntry
class Directory(IEntry):
def __init__(self, name):
self.name = name
self.list = []
def getName(self):
return self.name
def add(self, entry):
if isinstance(entry, IEntry):
self.list.append(entry)
def getList(self):
return self.list
def accept(self, v):
v.visitDirectory(self)
File.py from IEntry import IEntry
class File(IEntry):
def __init__(self, name, size):
self.name = name
self.size = size
def getName(self):
return self.name
def getSize(self):
return self.size
def accept(self, v):
v.visitFile(self)
ListVisitor.py class ListVisitor:
def __init__(self):
self.currentDir = ""
def visitFile(self, file):
print(f"{self.currentDir}{file.getName()} ({file.getSize()}MB)")
def visitDirectory(self, directory):
saveDir = self.currentDir
self.currentDir += directory.getName() + "\\"
list = directory.getList()
for entry in list:
entry.accept(self)
self.currentDir = saveDir
Main.py from File import File
from Directory import Directory
from ListVisitor import ListVisitor
file1 = File("file1", 10)
file2 = File("file2", 20)
file3 = File("file3", 30)
file4 = File("file4", 40)
dir1 = Directory("dir1")
dir1.add(file1)
dir2 = Directory("dir2")
dir2.add(file2)
dir2.add(file3)
dir1.add(dir2)
dir1.add(file4)
dir1.accept(ListVisitor())
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述したDirectory クラス、File クラス共に dir() メソッドの中にどう表示するか(Directory クラスはフォルダ名を\でつなげ、File クラスは括弧の中にファイルサイズを表示する)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Directory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visitDirectory が呼び出されます。visitDirectory からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visitDirectory が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visitFile が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、this キーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |
|
Visitorパターンを使用しない例 IEntry.php <?php
interface IEntry {
public function dir($path);
}
?>
Directory.php <?php
require_once('IEntry.php');
class Directoryx implements IEntry {
public function __construct($name) {
$this->name = $name;
$this->list = array();
}
public function add($entry) {
if ($entry instanceof IEntry)
$this->list[] = $entry);
}
public function dir($path) {
$path .= $this->name + "\\";
foreach ($this->list as $entry) {
$entry->dir($path);
}
}
}
?>
File.php <?php
class File implements IEntry {
public function __construct($name, $size) {
$this->name = $name;
$this->size = $size;
}
public function dir($path) {
printf( "%s%s (%dMB)\n", $path, $this->name, $this->size);
}
}
?>
Main.java <?php
require_once('File.php');
require_once('Directory.php');
$file1 = new File("file1", 10);
$file2 = new File("file2", 20);
$file3 = new File("file3", 30);
$file4 = new File("file4", 40);
$dir1 = new Directoryx("dir1");
$dir1->add($file1);
$dir2 = new Directoryx("dir2");
$dir2->add($file2);
$dir2->add($file3);
$dir1->add($dir2);
$dir1->add($file4);
$dir1->dir("");
?>
|
Visitorパターンを使用した例 IEntry.php <?php
interface IEntry {
public function accept($v);
public function getName();
}
?>
Directory.php <?php
require_once('IEntry.php');
class Directoryx implements IEntry {
public function __construct($name) {
$this->name = $name;
$this->list = array();
}
public function getName() {
return $this->name;
}
public function add($entry) {
if ($entry instanceof IEntry)
$this->list[] = $entry);
}
public function getList() {
return $this->list;
}
public function accept($v) {
$v->visitDirectory($this);
}
}
?>
File.php <?php
class File implements IEntry {
public function __construct($name, $size) {
$this->name = $name;
$this->size = $size;
}
public function getName() {
return $this->name;
}
public function getSize() {
return $this->size;
}
public function accept($v) {
$v->visitFile($this);
}
}
?>
ListVisitor.php <?php
class ListVisitor {
public function __construct() {
$this->currentDir = "";
}
public function visitFile($file) {
printf("%s%s (%dMB)\n", $this->currentDir, $file->name, $file->size);
}
public function visitDirectory($directory) {
$saveDir = $this->currentDir;
$this->currentDir .= $directory->getName() . "\\";
$list = $directory->getList();
foreach ($list as $entry) {
$entry->accept($this);
}
$this->currentDir = $saveDir;
}
}
?>
Main.java <?php
require_once('File.php');
require_once('Directory.php');
require_once('ListVisitor.php');
$file1 = new File("file1", 10);
$file2 = new File("file2", 20);
$file3 = new File("file3", 30);
$file4 = new File("file4", 40);
$dir1 = new Directoryx("dir1");
$dir1->add($file1);
$dir2 = new Directoryx("dir2");
$dir2->add($file2);
$dir2->add($file3);
$dir1->add($dir2);
$dir1->add($file4);
$dir1->accept(new ListVisitor());
?>
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directoryx クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、Fileをすべて表示します。 よって、Dir メソッドの中で、ファイルなのかディレクトリなのかを判断する必要がありますが、PHP は変数に型がないので、どんなクラスでもポリモーフィズムができてしまいます。よって、dir メソッドの中で、ファイルなのかディレクトリなのかを判断する必要がありません。 したがって、特に Compositeパターンを意識しなくても同様のことができてしまいます。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、File と Directoryx が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、dir メソッドのみを定義しています。これを実装する形で、Directoryx クラス、File クラスを作成すると、例のようになります。 Directoryx クラスは、内部にディレクトリやファイルを持てますので、add メソッドが追加されています。また、dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの dir メソッドを呼び出し、そのパスを表示します。インスタンスが File ならば、そのインスタンスの dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、dir メソッドがファイル名とサイズを表示します。 このように、Directoryx クラス、File クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directoryx クラスの dir メソッド内では、実態が File クラスのインスタンスであるのか、Directoryx クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directoryx クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、IEntry インタフェースを実装した Directoryx クラスで add メソッドを定義しています。実際、addメソッドを使えるのが Directoryx クラスだけだからです。しかし、 add メソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entryに実装し、例外を発生させる。
- addメソッドを Entryクラスに実装し、例外を発生させます。addメソッドを実際に使える Directoryx クラスでは、 Entryクラスのaddメソッドをオーバーライドして、意味のある実装に置き換えます。Fileクラスでは、 Entryクラスのaddメソッドを継承しているのでaddは可能ですが、例外が投げられます。
- 抽象クラス Entryに実装し、何も行わない。
- addメソッドを Entryクラスに実装するが、何も行わないようにすることもできます。そうすると、Fileクラスからのaddは例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entryで宣言はするが、実装はしない。
- addメソッドを Entryクラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directoryクラスにだけ記述する。
- この例題がそうです。addメソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要な Directoryx クラスにだけ入れるというやり方です。
|
Visitorパターンを使用しない例 IEntry.ts export
interface IEntry {
dir(path:string):void;
}
Directory.ts import {IEntry} from "./IEntry";
export
class Directory implements IEntry {
private name:string;
private list:Array<IEntry>;
public constructor(name:string) {
this.name = name;
this.list = new Array<IEntry>();
}
public add(entry:IEntry):void {
this.list.push(entry);
}
public dir(path:string):void {
path += this.name + "\\";
this.list.forEach(function(entry:IEntry) {entry.dir(path);}, this);
}
}
File.ts import {IEntry} from "./IEntry";
export
class File implements IEntry {
private name:string;
private size:number;
public constructor(name:string, size:number) {
this.name = name;
this.size = size;
}
public dir(path:string):void {
process.stdout.write(path + this.name + " (" + this.size + "MB)\n");
}
}
Main.ts import {File} from "./File";
import {Directory} from "./Directory";
let file1:File = new File("file1", 10);
let file2:File = new File("file2", 20);
let file3:File = new File("file3", 30);
let file4:File = new File("file4", 40);
let dir1:Directory = new Directory("dir1");
dir1.add(file1);
let dir2:Directory = new Directory("dir2");
dir2.add(file2);
dir2.add(file3);
dir1.add(dir2);
dir1.add(file4);
dir1.dir("");
|
Visitorパターンを使用した例 IEntry.ts import {IVisitor} from "./IVisitor";
export
interface IEntry {
accept(v:IVisitor):void;
getName():string;
}
Directory.ts import {IEntry} from "./IEntry";
import {IVisitor} from "./IVisitor";
export
class Directory implements IEntry {
private list:Array<IEntry> ;
private name:string;
public constructor(name:string) {
this.name = name;
this.list = new Array<IEntry>();
}
public getName():string {
return this.name;
}
public add(entry:IEntry):IEntry {
this.list.push(entry);
return this;
}
public getList():Array<IEntry> {
return this.list;
}
public accept(v:IVisitor):void {
v.visit(this);
}
}
File.ts import {IEntry} from "./IEntry";
import {IVisitor} from "./IVisitor";
export
class File implements IEntry {
private name:string;
private size:number;
public constructor(name:string, size:number) {
this.name = name;
this.size = size;
}
public getName():string {
return name;
}
public getSize():number {
return size;
}
public accept(v:IVisitor):void {
v.visit(this);
}
}
IVisitor.ts import {IEntry} from "./IEntry";
export
interface IVisitor {
visit(entry:IEntry):void;
}
ListVisitor.ts import {IEntry} from "./IEntry";
import {File} from "./File";
import {Directory} from "./Directory";
import {IVisitor} from "./IVisitor";
export
class ListVisitor implements IVisitor {
private currentDir:string = "";
public visit(entry:IEntry) {
if (entry instanceof File) {
process.stdout.write(this.currentDir + entry.getName() + " (" + entry.getSize() + "MB)\n");
}
else {
let saveDir:string = this.currentDir;
this.currentDir += entry.getName() + "\\";
let list:Array<IEntry> = (<Directory>entry).getList();
list.forEach(function(entry) { entry.accept(this); }, this);
this.currentDir = saveDir;
}
}
}
Main.ts import {File} from "./File";
import {Directory} from "./Directory";
import {ListVisitor} from "./ListVisitor";
let file1:File = new File("file1", 10);
let file2:File = new File("file2", 20);
let file3:File = new File("file3", 30);
let file4:File = new File("file4", 40);
let dir1:Directory = new Directory("dir1");
dir1.add(file1);
let dir2:Directory = new Directory("dir2");
dir2.add(file2);
dir2.add(file3);
dir1.add(dir2);
dir1.add(file4);
dir1.accept(new ListVisitor());
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述したDirectory クラス、File クラス共に dir() メソッドの中にどう表示するか(Directory クラスはフォルダ名を\でつなげ、File クラスは括弧の中にファイルサイズを表示する)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Directory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visit(Directory) が呼び出されます。visit(Directory) からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visit(Directory) が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visit(File) が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、thisキーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |
|
Visitorパターンを使用しない例 IEntry.swift public protocol IEntry {
dir(_ path:String)
}
Directory.swift public class Directory : IEntry {
private var name:String
private var list:[IEntry] = []
public init(_ name:String) {
self.name = name
}
public func add(_ entry:IEntry) {
list.append(entry)
}
public func dir(_ path:String) {
let p = path + name + "\\"
list.forEach {
$0.dir(p)
}
}
}
File.swift public class File : IEntry {
private var name:String
private var size:Int
public init(_ name:String, _ size:Int) {
self.name = name
self.size = size
}
public func dir(_ path:String) {
print(path + name + " (" + String(size) + "MB)")
}
}
Main.swift let file1:File = File("file1", 10)
let file2:File = File("file2", 20)
let file3:File = File("file3", 30)
let file4:File = File("file4", 40)
let dir1:Directory = Directory("dir1")
dir1.add(file1)
let dir2:Directory = Directory("dir2")
dir2.add(file2)
dir2.add(file3)
dir1.add(dir2)
dir1.add(file4)
dir1.dir("")
|
Visitorパターンを使用した例 IEntry.swift public protocol IEntry {
func accept(_ v:IVisitor)
func getName() -> String
}
Directory.swift public class Directory : IEntry {
private var list:[IEntry] = []
private var name:String
public init(_ name:String) {
self.name = name
}
public func getName() -> String {
return name
}
public func add(_ file:File) {
list.append(file)
}
public func add(_ dir:Directory) {
list.append(dir)
}
public func getList() -> [IEntry] {
return list
}
public func accept(_ v:IVisitor) {
v.visit(self)
}
}
File.swift public class File : IEntry {
private var name:String
private var size:Int
public init(_ name:String, _ size:Int) {
self.name = name
self.size = size
}
public func getName() -> String {
return name
}
public func getSize() -> Int {
return size
}
public func accept(_ v:IVisitor) {
v.visit(self)
}
}
IVisitor.swift public protocol IVisitor {
func visit(_ file:File)
func visit(_ dir:Directory)
}
ListVisitor.swift public class ListVisitor : IVisitor {
private var currentDir:String = ""
public func visit(_ file:File) {
print(currentDir + file.getName() + " (" + String(file.getSize()) + "MB)")
}
public func visit(_ dir:Directory) {
let saveDir:String = currentDir
currentDir += dir.getName() + "\\"
let list:[IEntry] = dir.getList()
list.forEach {
$0.accept(self)
}
currentDir = saveDir
}
}
Main.swift let file1:File = File("file1", 10)
let file2:File = File("file2", 20)
let file3:File = File("file3", 30)
let file4:File = File("file4", 40)
let dir1:Directory = Directory("dir1")
dir1.add(file1)
let dir2:Directory = Directory("dir2")
dir2.add(file2)
dir2.add(file3)
dir1.add(dir2)
dir1.add(file4)
dir1.accept(ListVisitor())
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述したDirectory クラス、File クラス共に dir() メソッドの中にどう表示するか(Directory クラスはフォルダ名を\でつなげ、File クラスは括弧の中にファイルサイズを表示する)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Directory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visit(Directory) が呼び出されます。visit(Directory) からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visit(Directory) が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visit(File) が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、thisキーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |
|
Visitorパターンを使用しない例 IEntry.kt interface IEntry {
fun dir(path: String)
}
Directory.kt class Directory(private val name: String) : IEntry {
private list: MutableList<IEntry> = ArrayList()
fun add(file:File) {
list.add(file)
}
fun add(dir:Directory) {
list.add(dir)
}
override fun dir(path: String) {
var path = path
path += name + "\\"
val it: Iterator<IEntry> = list.iterator()
while (it.hasNext()) {
val entry = it.next()
entry.dir(path)
}
}
}
File.kt class File(private val name: String, val size: Int) : IEntry {
override fun dir(path: String) {
println(path + name + " (" + size + "MB)")
}
}
Main.kt fun main() {
val file1 = File("file1", 10)
val file2 = File("file2", 20)
val file3 = File("file3", 30)
val file4 = File("file4", 40)
val dir1 = Directory("dir1")
dir1.add(file1)
val dir2 = Directory("dir2")
dir2.add(file2)
dir2.add(file3)
dir1.add(dir2)
dir1.add(file4)
dir1.dir("")
}
|
Visitorパターンを使用した例 IEntry.kt interface IEntry {
fun accept(v: IVisitor)
fun getName(): String
}
Directory.kt class Directory(private val name: String) : IEntry {
private var list: MutableList<IEntry> = ArrayList()
override fun getName(): String {
return name
}
fun add(entry: IEntry) : IEntry {
list.add(entry)
return this
}
operator fun iterator(): Iterator<IEntry> {
return list.iterator()
}
override fun accept(v: IVisitor) {
v.visit(this)
}
}
File.kt class File(private val name: String, val size: Int) : IEntry {
override fun getName(): String {
return name
}
internal fun getSize(): Int {
return size
}
override fun accept(v: IVisitor) {
v.visit(this)
}
}
IVisitor.kt interface IVisitor {
fun visit(file:File)
fun visit(directory:Directory)
}
ListVisitor.kt class ListVisitor : IVisitor {
private var currentDir: String = ""
override fun visit(file:File) {
println(currentDir + file.getName() + " (" + file.getSize() + "MB)")
}
override fun visit(directory: Directory) {
var saveDir: String = currentDir
currentDir += directory.getName() + "\\"
var it: Iterator<IEntry> = directory.iterator()
while (it.hasNext()) {
val entry: IEntry = it.next()
entry.accept(this)
}
currentDir = saveDir
}
}
Main.kt fun main() {
val file1 = File("file1", 10)
val file2 = File("file2", 20)
val file3 = File("file3", 30)
val file4 = File("file4", 40)
val dir1 = Directory("dir1")
dir1.add(file1)
val dir2 = Directory("dir2")
dir2.add(file2)
dir2.add(file3)
dir1.add(dir2)
dir1.add(file4)
dir1.accept(ListVisitor())
}
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述したDirectory クラス、File クラス共に dir() メソッドの中にどう表示するか(Directory クラスはフォルダ名を\でつなげ、File クラスは括弧の中にファイルサイズを表示する)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Directory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visit(Directory) が呼び出されます。visit(Directory) からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visit(Directory) が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visit(File) が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、thisキーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |
|
Visitorパターンを使用しない例 IEntry.scala trait IEntry {
def dir(path: String)
}
Directory.scala import java.util.ArrayList
class Directory(val name: String) extends IEntry {
private val list = new ArrayList[IEntry]()
def add(entry:IEntry) {
list.add(entry)
}
def dir(path: String) {
var p = path + name + "\\"
val it = list.iterator()
while (it.hasNext) {
val entry = it.next
entry.dir(p)
}
}
}
File.scala class File(val name: String, val size: Int) extends IEntry {
override fun dir(path: String) {
System.out.println(path + name + " (" + size + "MB)")
}
}
Main.scala object Main {
def main(args: Array[String]) {
val file1 = new File("file1", 10)
val file2 = new File("file2", 20)
val file3 = new File("file3", 30)
val file4 = new File("file4", 40)
val dir1 = new Directory("dir1")
dir1.add(file1)
val dir2 = new Directory("dir2")
dir2.add(file2)
dir2.add(file3)
dir1.add(dir2)
dir1.add(file4)
dir1.dir("")
}
}
|
Visitorパターンを使用した例 IEntry.scala trait IEntry {
def accept(v: IVisitor)
def getName(): String
}
Directory.scala import java.util.ArrayList
import java.util.Iterator
class Directory(val name: String) extends IEntry {
private var list = new ArrayList[IEntry]()
def getName(): String = name
def add(entry: IEntry) : IEntry = {
list.add(entry)
this
}
def iterator(): Iterator[IEntry] = list.iterator()
def accept(v: IVisitor) {
v.visit(this)
}
}
File.scala class File(val name: String, val size: Int) extends IEntry {
def getName(): String = name
def getSize(): Int = size
def accept(v: IVisitor) {
v.visit(this)
}
}
IVisitor.scala trait IVisitor {
def visit(file:File)
def visit(directory:Directory)
}
ListVisitor.scala class ListVisitor extends IVisitor {
private var currentDir: String = ""
def visit(file:File) {
System.out.println(currentDir + file.getName() + " (" + file.getSize() + "MB)")
}
def visit(directory: Directory) {
var saveDir: String = currentDir
currentDir += directory.getName() + "\\"
var it = directory.iterator()
while (it.hasNext) {
val entry: IEntry =it.next()
entry.accept(this)
}
currentDir = saveDir
}
}
Main.scala object Main {
def main(args: Array[String]) {
val file1 = new File("file1", 10)
val file2 = new File("file2", 20)
val file3 = new File("file3", 30)
val file4 = new File("file4", 40)
val dir1 = new Directory("dir1")
dir1.add(file1)
val dir2 = new Directory("dir2")
dir2.add(file2)
dir2.add(file3)
dir1.add(dir2)
dir1.add(file4)
dir1.accept(new ListVisitor())
}
}
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述したDirectory クラス、File クラス共に dir() メソッドの中にどう表示するか(Directory クラスはフォルダ名を\でつなげ、File クラスは括弧の中にファイルサイズを表示する)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Directory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visit(Directory) が呼び出されます。visit(Directory) からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visit(Directory) が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visit(File) が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、thisキーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |
|
Visitorパターンを使用しない例 IEntry.groovy interface IEntry {
void dir(String path)
}
Directory.groovy class Directory implements IEntry {
private List<IEntry> list = null
private String name = null
Directory(String name) {
this.name = name
list = new ArrayList<IEntry>()
}
void add(IEntry entry) {
list.add(entry)
}
void dir(String path) {
path += name + "\\"
Iterator<IEntry> it = list.iterator()
while (it.hasNext()) {
IEntry entry = it.next()
entry.dir(path)
}
}
}
File.groovy class File implements IEntry {
private String name = null
private int size = 0
File(String name, int size) {
this.name = name
this.size = size
}
void dir(String path) {
System.out.println(path + name + " (" + size + "MB)")
}
} Main.groovy class Main {
public static void main(String[] args) {
File file1 = new File("file1", 10)
File file2 = new File("file2", 20)
File file3 = new File("file3", 30)
File file4 = new File("file4", 40)
Directory dir1 = new Directory("dir1")
dir1.add(file1)
Directory dir2 = new Directory("dir2")
dir2.add(file2)
dir2.add(file3)
dir1.add(dir2)
dir1.add(file4)
dir1.dir("")
}
}
|
Visitorパターンを使用した例 IEntry.groovy interface IEntry {
void accept(IVisitor v)
}
Directory.groovy class Directory implements IEntry {
private List<IEntry> list = null
protected String name = null
Directory(String name) {
this.name = name
list = new ArrayList<IEntry>()
}
IEntry add(IEntry entry) {
list.add(entry)
return this
}
Iterator<IEntry> iterator() {
return list.iterator()
}
void accept(IVisitor v) {
v.visit(this)
}
}
File.groovy class File implements IEntry {
protected String name = null
protected int size = 0
File(String name, int size) {
this.name = name
this.size = size
}
void accept(IVisitor v) {
v.visit(this)
}
}
IVisitor.groovy interface IVisitor {
void visit(File file)
void visit(Directory directory)
}
ListVisitor.groovy class ListVisitor implements IVisitor {
private String currentDir = ""
void visit(File file) {
System.out.println(currentDir + file.name + " (" + file.size + "MB)")
}
void visit(Directory directory) {
String saveDir = currentDir
currentDir += directory.name + "\\"
Iterator<IEntry> it = directory.iterator()
while (it.hasNext()) {
IEntry entry = (IEntry) it.next()
entry.accept(this)
}
currentDir = saveDir
}
}
Main.groovy class Main {
static void main(String[] args) {
File file1 = new File("file1", 10)
File file2 = new File("file2", 20)
File file3 = new File("file3", 30)
File file4 = new File("file4", 40)
Directory dir1 = new Directory("dir1")
dir1.add(file1)
Directory dir2 = new Directory("dir2")
dir2.add(file2)
dir2.add(file3)
dir1.add(dir2)
dir1.add(file4)
dir1.accept(new ListVisitor())
}
}
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述したDirectory クラス、File クラス共に dir() メソッドの中にどう表示するか(Directory クラスはフォルダ名を\でつなげ、File クラスは括弧の中にファイルサイズを表示する)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Directory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visit(Directory) が呼び出されます。visit(Directory) からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visit(Directory) が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visit(File) が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、thisキーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |
|
Visitorパターンを使用しない例 IEntry.go type IEntry interface {
Dir(string)
}
Directory.go type Directory struct {
IEntry
list []IEntry
name string
}
func (self *Directory) Add(entry IEntry) {
self.list = append(self.list, entry)
}
func (self *Directory) Dir(path string) {
path += self.name + "\\"
for _, entry := range self.list {
entry.Dir(path)
}
}
func NewDirectory(name string) {
return &Directory {
name: name,
}
}
File.go import "fmt"
type File struct {
IEntry
name string
size int
}
func (self *File) Dir(path string) {
fmt.Printf("%s%s (%dMB)\n", path, self.name, self.size)
}
func NewFile(name string, size int) *File {
return &File {
name: name,
size: size,
}
}
Main.go func main() {
var file1 = NewFile("file1", 10)
var file2 = NewFile("file2", 20)
var file3 = NewFile("file3", 30)
var file4 = NewFile("file4", 40)
var dir1 = NewDirectory("dir1")
dir1.Add(file1)
var dir2 = NewDirectory("dir2")
dir2.Add(file2)
dir2.Add(file3)
dir1.Add(dir2)
dir1.Add(file4)
dir1.Dir("")
}
|
Visitorパターンを使用した例 IEntry.go type IEntry interface {
Accept(IVisitor)
Dir(string)
}
Directory.go type Directory struct {
IEntry
List []IEntry
Name string
}
func (self *Directory) Add(entry IEntry) {
self.list = append(self.list, entry)
return self
}
func (self *Directory) Accept(v IVisitor) {
v.VisitDir(self)
}
func NewDirectory(name string) *Directory {
return &Directory {
Name: name,
}
}
File.go import "fmt"
type File struct {
IEntry
Name string
Size int
}
func (self *File) Accept(v IVisitor) {
v.VisitFile(this)
}
func NewFile(name string, size int) {
return &File {
Name: name,
Size: size,
}
}
IVisitor.go type IVisitor interface {
VisitFile(*File)
VisitDir(*Directory)
}
ListVisitor.go import "fmt"
type ListVisitor struct {
IVisitor
currentDir string
}
func (self *ListVisitor) VisitFile(file *File) {
fmt.Printf("%s%s (%dMB)\n", self.currentDir, file.Name, file.Size)
}
func (self *ListVisitor) VisitDir(directory *Directory) {
var saveDir = self.currentDir
self.currentDir += directory.Name + "\\"
for _, entry := range directory.List {
entry.Accept(self)
}
self.currentDir = saveDir
}
func NewListVisitor() *ListVisitor {
return &ListVisitor{}
}
Main.go class main() {
var file1 = NewFile("file1", 10)
var file2 = NewFile("file2", 20)
var file3 = NewFile("file3", 30)
var file4 = NewFile("file4", 40)
var dir1 = NewDirectory("dir1")
dir1.Add(file1)
var dir2 = NewDirectory("dir2")
dir2.Add(file2)
dir2.Add(file3)
dir1.Add(dir2)
dir1.Add(file4)
dir1.Accept(NewListVisitor())
}
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述したDirectory クラス、File クラス共に dir() メソッドの中にどう表示するか(Directory クラスはフォルダ名を\でつなげ、File クラスは括弧の中にファイルサイズを表示する)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Directory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visit(Directory) が呼び出されます。visit(Directory) からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visit(Directory) が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visit(File) が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、thisキーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |
|
Visitorパターンを使用しない例 ientry.d public interface IEntry {
public void dir(in string path);
}
directory.d import ientry;
public class Directory : IEntry {
private IEntry[] list;
private string name = null;
public this(string name) {
this.name = name;
}
public void add(IEntry entry) {
list ~= entry;
}
public void dir(string path) {
path ~= name ~ "\\";
foreach (IEntry entry ; list) {
entry.dir(path);
}
}
}
file.d import std.stdio;
import ientry;
public class File : IEntry {
private string name = null;
private int size = 0;
public this(in string name, in int size) {
this.name = name;
this.size = size;
}
public void dir(in string path) {
writefln("%s%s (%dMB)", path, name, size);
}
}
main.d import std.stdio : writefln;
import directory;
import file;
public int main() {
File file1 = new File("file1", 10);
File file2 = new File("file2", 20);
File file3 = new File("file3", 30);
File file4 = new File("file4", 40);
Directory dir1 = new Directory("dir1");
dir1.add(file1);
Directory dir2 = new Directory("dir2");
dir2.add(file2);
dir2.add(file3);
dir1.add(dir2);
dir1.add(file4);
dir1.dir("");
return 0;
}
|
Visitorパターンを使用した例 ientry.d import ivisitor;
public interface IEntry {
void accept(IVisitor v);
string getName();
}
directory.d import ientry;
import ivisitor;
public class Directory : IEntry {
private IEntry[] list;
private string name = null;
public this(in string name) {
this.name = name;
}
public string getName() {
return name;
}
public IEntry add(IEntry entry) {
list ~= entry;
return this;
}
public IEntry[] getList() {
return list;
}
public void accept(IVisitor v) {
v.visit(this);
}
}
file.d import ientry;
import ivisitor;
public class File : IEntry {
private string name = null;
private int size = 0;
public this(in string name, in int size) {
this.name = name;
this.size = size;
}
public string getName() {
return name;
}
public int getSize() {
return size;
}
public void accept(IVisitor v) {
v.visit(this);
}
}
ivisitor.d import file;
import directory;
public interface IVisitor {
void visit(File file);
void visit(Directory directory);
}
listvisitor.d import std.stdio : writefln;
import ivisitor;
import ientry;
import file;
import directory;
public class ListVisitor : IVisitor {
private string currentDir = "";
public void visit(File file) {
writefln("%s%s (%dMB)", currentDir, file.getName(), file.getSize());
}
public void visit(Directory directory) {
string saveDir = currentDir;
currentDir ~= directory.getName() ~ "\\";
IEntry[] list = directory.getList();
foreach (IEntry entry; list) {
entry.accept(this);
}
currentDir = saveDir;
}
}
main.d import listvisitor;
import file;
import directory;
int main() {
File file1 = new File("file1", 10);
File file2 = new File("file2", 20);
File file3 = new File("file3", 30);
File file4 = new File("file4", 40);
Directory dir1 = new Directory("dir1");
dir1.add(file1);
Directory dir2 = new Directory("dir2");
dir2.add(file2);
dir2.add(file3);
dir1.add(dir2);
dir1.add(file4);
dir1.accept(new ListVisitor());
return 0;
}
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述したDirectory クラス、File クラス共に dir() メソッドの中にどう表示するか(Directory クラスはフォルダ名を\でつなげ、File クラスは括弧の中にファイルサイズを表示する)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Directory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visit(Directory) が呼び出されます。visit(Directory) からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visit(Directory) が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visit(File) が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、thisキーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |
|
Visitorパターンを使用しない例 IEntry.pas unit UnitIEntry;
interface
type
IEntry = interface
procedure dir(path: string);
end;
implementation
end.
Directory.pas unit UnitDirectory;
interface
uses
System.Generics.Collections,
UnitIEntry,
UnitFile;
type
Directory = class(TInterfacedObject, IEntry)
private
var list:TList<IEntry>;
var name:string;
public
constructor Create(name:string);
destructor Destroy(); override;
procedure add(entry:IEntry); overload;
procedure dir(path:string);
end;
implementation
constructor Directory.Create(name:string);
begin
self.name := name;
list := TList<IEntry>.Create();
end;
destructor Directory.Destroy();
begin
list.Free;
end;
procedure Directory.add(entry:IEntry);
begin
list.Add(entry);
end;
procedure Directory.dir(path:string);
var entry:IEntry;
begin
path := path + name + '\';
for entry in list do begin
entry.dir(path);
end;
end;
end.
File.pas unit UnitFile;
interface
uses
System.SysUtils,
UnitIEntry;
type
Filex = class(TInterfacedObject, IEntry)
private
var name:string;
var size:integer;
public
constructor Create(name:string; size:integer);
procedure dir(path:string);
end;
implementation
constructor Filex.Create(name:string; size:integer);
begin
self.name := name;
self.size := size;
end;
procedure Filex.dir(path:string);
begin
Writeln(Format('%s%s(%dMB)', [path, name, size]));
end;
end.
Main.dpr program Main;
uses
System.SysUtils,
UnitDirectory,
UnitFile;
var file1:Filex;
var file2:Filex;
var file3:Filex;
var file4:Filex;
var dir1:Directory;
var dir2:Directory;
begin
file1 := Filex.Create('file1', 10);
file2 := Filex.Create('file2', 20);
file3 := Filex.Create('file3', 30);
file4 := Filex.Create('file4', 40);
dir1 := Directory.Create('dir1');
dir1.add(file1);
dir2 := Directory.Create('dir2');
dir2.add(file2);
dir2.add(file3);
dir1.add(dir2);
dir1.add(file4);
dir1.dir('');
dir1.Free;
end.
|
Visitorパターンを使用した例 IEntry.pas unit UnitIEntry;
interface
uses
UnitIVisitor;
type
IEntry = interface
procedure accept(v:IVisitor);
function getName():string;
end;
implementation
end.
Directory.pas unit UnitDirectory;
interface
uses
System.Generics.Collections,
UnitIEntry,
UnitIVisitor;
type
Directory = class(TInterfacedObject, IEntry)
private
var list:TList<IEntry>;
var name:string;
public
constructor Create(name:string);
destructor Destroy(); override;
function getName():string;
procedure add(entry:IEntry);
function getList():TList<IEntry>;
procedure accept(v:IVisitor);
end;
implementation
constructor Directory.Create(name:string);
begin
self.name := name;
list := TList<IEntry>.Create();
end;
destructor Directory.Destroy();
begin
list.Free;
end;
function Directory.getName():string;
begin
Result := name;
end;
procedure Directory.add(entry:IEntry);
begin
list.add(entry);
end;
function Directory.getList():TList<IEntry>;
begin
Result := list;
end;
procedure Directory.accept(v:IVisitor);
begin
v.visit(self);
end;
end.
File.pas unit UnitFile;
interface
uses
System.SysUtils,
UnitIEntry,
UnitIVisitor;
type
Filex = class(TInterfacedObject, IEntry)
private
var name:string;
var size:integer;
public
constructor Create(name:string; size:integer);
function getName():string;
function getSize():integer;
procedure accept(v:IVisitor);
end;
implementation
constructor Filex.Create(name:string; size:integer);
begin
self.name := name;
self.size := size;
end;
function Filex.getName():string;
begin
Result := name;
end;
function Filex.getSize():integer;
begin
Result := size;
end;
procedure Filex.accept(v:IVisitor);
begin
v.visit(self);
end;
end.
IVisitor.pas unit UnitIVisitor;
interface
type
IVisitor = interface
procedure visit(entry:TObject); // 循環参照が起きるため、TObject に変更
end;
implementation
end.
ListVisitor.pas unit UnitListVisitor;
interface
uses
System.Generics.Collections,
System.SysUtils,
UnitIVisitor;
type
ListVisitor = class(TInterfacedObject, IEntry)
private
var currentDir:string;
public
constructor Create();
procedure visit(entry:TObject); // 循環参照が起きるため、TObject に変更
end;
implementation
uses
UnitIEntry,
UnitFile,
UnitDirectory;
constructor ListVisitor.Create();
begin
currentDir := '';
end;
procedure IVisitor.visit(entry:TObject);
var saveDir:string;
var list:TList<IEntry>;
var fl:Filex;
var dr:Directory;
var e:IEntry;
begin
if entry is Filex then begin
fl := entry as Filex;
Writeln(Format('%s%s(%dMB)', [currentDir, fl.getName(), fl.getSize()]));
end
else begin
dr := entry as Directory;
saveDir := currentDir;
currentDir := currentDir + dr.getName() + '\';
list := dr.getList();
for e in list do begin
e.accept(self);
end;
currentDir := saveDir;
end;
end;
end.
Main.dpr program VisitorGof;
uses
System.SysUtils,
UnitDirectory,
UnitFile,
UnitListVisitor,
UnitIEntry;
var file1:Filex;
var file2:Filex;
var file3:Filex;
var file4:Filex;
var dir1:Directory;
var dir2:Directory;
begin
file1 := Filex.Create('file1', 10);
file2 := Filex.Create('file2', 20);
file3 := Filex.Create('file3', 30);
file4 := Filex.Create('file4', 40);
dir1 := Directory.Create('dir1');
dir1.add(file1 as IEntry);
dir2 := Directory.Create('dir2');
dir2.add(file2);
dir2.add(file3);
dir1.add(dir2);
dir1.add(file4);
dir1.dir(ListVisitor.Create());
dir1.Free;
end.
|
|
まずは、Composite パターンを利用して上記のファイル構造を記述しました。しかし、ファイル構造を記述したDirectory クラス、File クラス共に dir() メソッドの中にどう表示するか(Directory クラスはフォルダ名を\でつなげ、File クラスは括弧の中にファイルサイズを表示する)という処理も書かれてしまっています。 |
Visitor パターンでは、 データ構造は Directory クラス、File クラス、処理は ListVisitor クラスに分けて書かれています。一般に Visitor クラスの実装は、データ構造の Directory クラス、File クラスとは別に開発することができます。つまり、部品として独立性を高めていることになります。もし、処理内容を Directory クラス、File クラスのメソッドとしてプログラムしてしますと( Composite パターンがそうですが…)、新しい「処理」を追加して機能拡張するたびに、 Directory クラス、File クラスを修正しなくてはならなくなります。 最初に呼ばれる Directory インスタンスの accept() からは、引数で渡された ListVisitor インスタンスの visit(Directory) が呼び出されます。visit(Directory) からは、次のオブジェクトが、ディレクトリならば Directory インスタンスの、ファイルならば File インスタンスの accept() が呼び出されます。そして、Directory インスタンスの accept() からは、また ListVisitor インスタンスの visit(Directory) が呼び出されますし、File インスタンスの accept() からは、ListVisitor インスタンスの visit(File) が呼び出され、ファイル名とサイズが表示されます。 メソッドの呼び出しをもう一度整理してみましょう。この例では、
としています。これにより、相手のオブジェクトに、thisキーワードを使って、自分自身をすべて引き渡しています。そして、相手のオブジェクトは、自分のメソッドを呼び出して処理を行います。これは、データ構造を持つオブジェクトとその処理をするオブジェクトとを分離することを目的としています。そうすることでデータ構造を持つオブジェクトをシンプルに保ち、そのデータ構造に対する処理を外出しにして、処理を追加しやすくしているのです。 |