Compositeパターン
再帰的な構造の取り扱いを容易にする
Composite とは、英語で「複合物」を意味する言葉です。 Composite パターンは、「中身と容器を同一視する」ことで、再帰的な構造の取り扱いを容易にするものです。具体的に言いますと、 「中身」とは、個々のオブジェクトであり、「容器」とは、それらオブジェクトが合成したものです。また、「同一視する」とは、すべてのオブジェクトに共通するインタフェースを持つことを意味します。
アプリケーションで扱うデータをモデル化してみると、オブジェクト同士の関係を単純な1対1の関係だけで管理できることはほとんどありません。多くのオブジェクトは、他のいくつかのオブジェクトへの関わりを持つことで構造を作っています。この構造が単純なうちはよいのですが、また別のオブジェクトへと次々に関わりを持っていくことで、構造はどんどん複雑になっていき、直感的に実装したり使ったりすることが難しくなります。
そんなときは Composite パターンが威力を発揮します。Composite パターンは、要素であるオブジェクトと、複数の要素からなる複合オブジェクトを区別なく扱えるという特徴を持ちます。この特徴を利用することで、構造を再帰的に組み立て、クライアントからの見た目をシンプルに保つことができます。

ただし、再帰構造であっても、容器と中身を同一視しない場合は、Compositeパターンではありません。再帰構造自体は、オブジェクト指向でなくても用いられ、あるメソッドが自分自身を呼び出すという形で使われます。このとき容器か中身かを判断する条件文が使われているのですが、Compositeパターンを使うことで、その条件文を排除し、ポリモーフィズムによって、容器と中身による実行内容の違いを実現します。
こうした例として、ファイルシステムがあります。 ファイルは中身ですが、フォルダは他のフォルダの中身でもあり、自分の持つファイルの容器でもあります。
また、家族関係などもこうした例です。親子三代の家族です。一代目の夫婦は、二代目の親です。しかし、二代目の夫婦は、一代目のおじいさん、おばあさんから見れば子供ですが、三代目の孫の親でもあります。
アプリケーションを作っていると、コンポジットパターンが適用できそうな再帰的構造を持つデータに出会う機会は非常に多いと言えます。身近なところでは上に挙げたファイルとフォルダの関係がそうですし、XMLで表現されるような構造を持つデータは、コンポジットパターンが適用できる好例です。
膨大な規模のデータ構造を目の当たりにすると、とかく複雑に考えがちですが、構成要素をよく観察し、コンポジットパターンが適用できないか一度考えてみましょう。
例題
dir1
├ file1
├ dir2
│ ├ file2
│ └ file3
└ file4
ファイルシステムは、ディレクトリとファイルの階層構造でできています。指定されたディレクトリの中のファイルとそのサイズを表示するクラスを作成しなさい。もちろん、さらに下層のディレクトリの中のファイルも表示します。
実行結果
dir1\dir2\file2 (20MB)
dir1\dir2\file3 (30MB)
dir1\file4 (40MB)
|
Compositeパターンを使用しない例 Directory.java public class Directory {
private List<Object> list = null;
private String name = null;
public Directory(String name) {
this.name = name;
list = new ArrayList<Object>();
}
public void add(File file) {
list.add(file);
}
public void add(Directory dir) {
list.add(dir);
}
public void dir(String path) {
path += name + "\\";
Iterator<Object> it = list.iterator();
while (it.hasNext()) {
Object obj = it.next();
if (obj instanceof File) {
((File) obj).dir(path);
}
else if (obj instanceof Directory) {
((Directory) obj).dir(path);
}
}
}
}
File.java public class File {
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("");
}
}
|
Compositeパターンを使用した例 IEntry.java public interface IEntry {
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("");
}
}
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、Fileをすべて表示します。 これでも問題なく動作します。しかし、dir メソッドの中で、ファイルなのかディレクトリなのかを判断していますので、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。Directory クラスは、add(Shortcut link) メソッドを追加したり、dir メソッドの中で、 Shortcut に対応できるようにしなければならなくなります。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、File と Directory が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、dir メソッドのみを定義しています。これを実装する形で、Directory クラス、File クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、add メソッドが追加されています。また、dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの dir メソッドを呼び出し、そのパスを表示します。インスタンスが File ならば、そのインスタンスの dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、File クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、IEntry インタフェースを実装した Directoryクラスで addメソッドを定義しています。実際、addメソッドを使えるのが Directoryクラスだけだからです。しかし、 addメソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entryに実装し、例外を発生させる。
- addメソッドを Entryクラスに実装し、例外を発生させます。addメソッドを実際に使える Directoryクラスでは、 Entryクラスのaddメソッドをオーバーライドして、意味のある実装に置き換えます。Fileクラスでは、 Entryクラスのaddメソッドを継承しているのでaddは可能ですが、例外が投げられます。
- 抽象クラス Entryに実装し、何も行わない。
- addメソッドを Entryクラスに実装するが、何も行わないようにすることもできます。そうすると、Fileクラスからのaddは例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entryで宣言はするが、実装はしない。
- addメソッドを Entryクラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directoryクラスにだけ記述する。
- この例題がそうです。addメソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要なDirectoryクラスにだけ入れるというやり方です。ただし、このやり方だと、Directoryのインスタンスが Entry型の変数に入っているときは、 Directory型にキャストしないとaddメソッドが使えないことになります。
|
Compositeパターンを使用しない例 directory.h #include <list>
#include <string>
#include "file.h"
class Directory
{
private:
std::string name;
std::list<void*> entryList;
public:
int type;
Directory(void);
Directory(std::string);
virtual ~Directory(void);
void add(file*);
void add(Directory*);
void dir(std::string);
};
directory.cpp #include "directory.h"
#include "file.h"
using namespace std;
Directory::Directory(void) {}
Directory::Directory(string name) : name(name), type(0) {}
Directory::~Directory(void) {}
void Directory::add(File* f) {
entryList.push_back((void*)f);
}
void Directory::add(Directory* d){
entryList.push_back((void*)d);
}
void Directory::dir(string path){
path += name + "\\";
list<void*>::iterator it = entryList.begin();
while(it != entryList.end()){
void* obj = *it++;
if(((Directory*)obj)->type == 0) {
((Directory*)obj)->dir(path);
}
else if(((File*)obj)->type == 1) {
((File*)obj)->dir(path);
}
}
}
file.h #include <string>
class File
{
private:
std::string name;
int size;
public:
int type;
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 name, int size) : name(name), size(size), type(1) {}
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;
}
|
Compositeパターンを使用した例 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;
}
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、list オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、fileをすべて表示します。 これでも問題なく動作します。しかし、dir メソッドの中で、ファイルなのかディレクトリなのかを判断していますので、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。Directory クラスは、add(Shortcut link) メソッドを追加したり、dir メソッドの中で、Shortcut に対応できるようにしなければならなくなります。 |
Composite パターンでは、容器と中身を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。それでは、File と Directory が共通のインタフェースIEntry クラスを実装しています。 IEntry クラスでは、dir メソッドのみを定義しています。これを実装する形で、Directory クラス、File クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、add メソッドが追加されています。また、dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの dir メソッドを呼び出し、そのパスを表示します。インスタンスが File ならば、そのインスタンスの dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、File クラスを共に IEntry クラスを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを 実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、Directory クラスで add メソッドを定義しています。実際、add メソッドを使えるのが Directory クラスだけだからです。しかし、 add メソッドの置き方、実装の仕方にはいろいろな場合があります。
- IEntry クラスに実装し、例外を発生させる。
- add メソッドを IEntry クラスに実装し、例外を発生させます。add メソッドを実際に使える Directory クラスでは、 IEntry クラスの add メソッドをオーバーライドして、意味のある実装に置き換えます。File クラスでは、 IEntry クラスの add メソッドを継承しているので add は可能ですが、例外が投げられます。
- IEntry に実装し、何も行わない。
- add メソッドを IEntry クラスに実装するが、何も行わないようにすることもできます。そうすると、File クラスからの add は例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス IEntry で宣言はするが、実装はしない。
- add メソッドを IEntry クラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directory クラスにだけ記述する。
- この例題がそうです。add メソッドを 抽象クラス IEntry クラスには入れずに、本当に必要な Directory クラスにだけ入れるというやり方です。ただし、このやり方だと、Directory のインスタンスが IEntry 型の変数に入っているときは、 dir型にキャストしないと add メソッドが使えないことになります。
|
Compositeパターンを使用しない例 Directory.cs class Directory
{
private ArrayList list;
private string name;
public Directory(string name)
{
this.name = name;
list = new ArrayList();
}
public void Add(File file)
{
list.Add(file);
}
public void Add(Directory dir)
{
list.Add(dir);
}
public void Dir(string path)
{
path += name + "/";
foreach(Object obj in list)
{
if (obj is File)
((File)obj).Dir(path);
else if (obj is Directory)
((Directory)obj).Dir(path);
}
}
}
File.cs class File
{
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 + ")");
}
}
Program.cs class Program
{
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("");
}
}
|
Compositeパターンを使用した例 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 + ")");
}
}
Program.cs class Program
{
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("");
}
}
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、Dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、Dir メソッドが呼ばれた場合には、Fileをすべて表示します。 これでも問題なく動作します。しかし、Dir メソッドの中で、ファイルなのかディレクトリなのかを判断していますので、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。Directory クラスは、Add(Shortcut link) メソッドを追加したり、Dir メソッドの中で、 Shortcut に対応できるようにしなければならなくなります。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、File と Directory が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、Dir メソッドのみを定義しています。これを実装する形で、Directory クラス、File クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、Add メソッドが追加されています。また、Dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの Dir メソッドを呼び出し、そのパスを表示します。インスタンスが File ならば、そのインスタンスの Dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、Dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、File クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、IEntry インタフェースを実装した Directory クラスで Add メソッドを定義しています。実際、Add メソッドを使えるのが Directory クラスだけだからです。しかし、 Add メソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entry に実装し、例外を発生させる。
- Add メソッドを Entry クラスに実装し、例外を発生させます。Add メソッドを実際に使える Directoryクラスでは、 Entry クラスの Add メソッドをオーバーライドして、意味のある実装に置き換えます。File クラスでは、 Entry クラスの Add メソッドを継承しているので Add は可能ですが、例外が投げられます。
- 抽象クラス Entry に実装し、何も行わない。
- Add メソッドを Entry クラスに実装するが、何も行わないようにすることもできます。そうすると、File クラスからの Add は例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entry で宣言はするが、実装はしない。
- Add メソッドを Entry クラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directory クラスにだけ記述する。
- この例題がそうです。Add メソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要な Directory クラスにだけ入れるというやり方です。ただし、このやり方だと、Directory のインスタンスが Entry 型の変数に入っているときは、 Directory 型にキャストしないと Add メソッドが使えないことになります。
|
Compositeパターンを使用しない例 Directory.vb Public Class Directory
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 file As File)
list.Add(file)
End Sub
Public Sub Add(ByVal dir As Directory)
list.Add(dir)
End Sub
Public Sub Dir(ByVal path As String)
path &= name & "\"
For Each obj As Object In list
If TypeOf obj Is File Then
DirectCast(obj, File).dir(path)
ElseIf TypeOf obj Is Directory Then
DirectCast(obj, Directory).Dir(path)
End If
Next
End Sub
End Class
File.vb Public Class File
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)
Console.WriteLine(path & name & "(" & size & "MB)")
End Sub
End Class
Program.vb Module Main
Sub Main()
Dim file1 As File = New File("file1", 10)
Dim file2 As File = New File("file2", 20)
Dim file3 As File = New File("file3", 30)
Dim file4 As File = New File("file4", 40)
Dim dir1 As Directory = New Directory("dir1")
dir1.Add(file1)
Dim dir2 As Directory = New Directory("dir2")
dir2.Add(file2)
dir2.Add(file3)
dir1.Add(dir2)
dir1.Add(file4)
dir1.Dir("")
End Sub
End Module
|
Compositeパターンを使用した例 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 file1 As File = New File("file1", 10)
Dim file2 As File = New File("file2", 20)
Dim file3 As File = New File("file3", 30)
Dim file4 As File = New File("file4", 40)
Dim dir1 As Directory = New Directory("dir1")
dir1.Add(file1)
Dim dir2 As Directory = New Directory("dir2")
dir2.Add(file2)
dir2.Add(file3)
dir1.Add(dir2)
dir1.Add(file4)
dir1.Dir("")
End Sub
End Module
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、Dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、Dir メソッドが呼ばれた場合には、Fileをすべて表示します。 これでも問題なく動作します。しかし、Dir メソッドの中で、ファイルなのかディレクトリなのかを判断していますので、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。Directory クラスは、Add(Shortcut link) メソッドを追加したり、Dir メソッドの中で、 Shortcut に対応できるようにしなければならなくなります。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、File と Directory が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、Dir メソッドのみを定義しています。これを実装する形で、Directory クラス、File クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、Add メソッドが追加されています。また、Dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの Dir メソッドを呼び出し、そのパスを表示します。インスタンスが File ならば、そのインスタンスの Dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、Dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、File クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、IEntry インタフェースを実装した Directory クラスで Add メソッドを定義しています。実際、Add メソッドを使えるのが Directory クラスだけだからです。しかし、 Add メソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entry に実装し、例外を発生させる。
- Add メソッドを Entry クラスに実装し、例外を発生させます。Add メソッドを実際に使える Directoryクラスでは、 Entry クラスの Add メソッドをオーバーライドして、意味のある実装に置き換えます。File クラスでは、 Entry クラスの Add メソッドを継承しているので Add は可能ですが、例外が投げられます。
- 抽象クラス Entry に実装し、何も行わない。
- Add メソッドを Entry クラスに実装するが、何も行わないようにすることもできます。そうすると、File クラスからの Add は例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entry で宣言はするが、実装はしない。
- Add メソッドを Entry クラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directory クラスにだけ記述する。
- この例題がそうです。Add メソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要な Directory クラスにだけ入れるというやり方です。ただし、このやり方だと、Directory のインスタンスが Entry 型の変数に入っているときは、 Directory 型にキャストしないと Add メソッドが使えないことになります。
|
Compositeパターンを使用しない例 Directory.js const File = require("./File.js");
module.exports = class Directory {
constructor(name) {
this.name = name;
this.list = new Array();
}
add(entry) {
if (entry instanceof File || entry instanceof Directory)
this.list.push(entry);
}
dir(path) {
path += this.name + "\\";
this.path = path;
this.list.forEach(
function(entry) { entry.dir(this.path); },
this);
}
}
File.js module.exports = class File {
constructor(name, size) {
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("");
|
Compositeパターンを使用した例 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) {
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) {
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("");
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、Fileをすべて表示します。 よって、Dir メソッドの中で、ファイルなのかディレクトリなのかを判断する必要がありますが、Javascript は変数に型がないので、どんなクラスでもポリモーフィズムができてしまいます。よって、dir メソッドの中では、ファイルなのかディレクトリなのかを判断する必要がありません。 また、オーバーロードする必要もありませんので add メソッドも1つだけですが、意図しないクラスが add されないように、File クラスか Directory クラスかは判定しています。 これでも問題なく動作します。しかし、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。add メソッドでショートカットも許可するようにしなければならなくなります。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、File と Directory が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、dir メソッドのみを定義しています。これを実装する形で、Directory クラス、File クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、add メソッドが追加されています。また、dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの dir メソッドを呼び出し、そのパスを表示します。インスタンスが File ならば、そのインスタンスの dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、File クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、IEntry インタフェースを実装した Directoryクラスで addメソッドを定義しています。実際、addメソッドを使えるのが Directoryクラスだけだからです。しかし、 addメソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entryに実装し、例外を発生させる。
- addメソッドを Entryクラスに実装し、例外を発生させます。addメソッドを実際に使える Directoryクラスでは、 Entryクラスのaddメソッドをオーバーライドして、意味のある実装に置き換えます。Fileクラスでは、 Entryクラスのaddメソッドを継承しているのでaddは可能ですが、例外が投げられます。
- 抽象クラス Entryに実装し、何も行わない。
- addメソッドを Entryクラスに実装するが、何も行わないようにすることもできます。そうすると、Fileクラスからのaddは例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entryで宣言はするが、実装はしない。
- addメソッドを Entryクラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directoryクラスにだけ記述する。
- この例題がそうです。addメソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要なDirectoryクラスにだけ入れるというやり方です。ただし、このやり方だと、Directoryのインスタンスが Entry型の変数に入っているときは、 Directory型にキャストしないとaddメソッドが使えないことになります。
|
Compositeパターンを使用しない例 Directory.pm package Directory {
sub new {
my ($class, $name) = @_;
my $this = { name => $name, list => () };
return bless $this, $class;
}
sub add {
my ($this, $entry) = @_;
if ($entry->isa("File") || $entry->isa("Directory")) {
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 {
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("");
|
Compositeパターンを使用した例 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("");
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、Fileをすべて表示します。 よって、Dir メソッドの中で、ファイルなのかディレクトリなのかを判断する必要がありますが、Perl は変数に型がないので、どんなクラスでもポリモーフィズムができてしまいます。よって、dir メソッドの中で、ファイルなのかディレクトリなのかを判断する必要がありません。 また、オーバーロードする必要もありませんので add メソッドも1つだけですが、意図しないクラスが add されないように、File クラスか Directory クラスかは判定しています。 これでも問題なく動作します。しかし、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。add メソッドでショートカットも許可するようにしなければならなくなります。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、File と Directory が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、dir メソッドのみを定義しています。これを実装する形で、Directory クラス、File クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、add メソッドが追加されています。また、dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの dir メソッドを呼び出し、そのパスを表示します。インスタンスが File ならば、そのインスタンスの dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、File クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、IEntry インタフェースを実装した Directoryクラスで addメソッドを定義しています。実際、addメソッドを使えるのが Directoryクラスだけだからです。しかし、 addメソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entryに実装し、例外を発生させる。
- addメソッドを Entryクラスに実装し、例外を発生させます。addメソッドを実際に使える Directoryクラスでは、 Entryクラスのaddメソッドをオーバーライドして、意味のある実装に置き換えます。Fileクラスでは、 Entryクラスのaddメソッドを継承しているのでaddは可能ですが、例外が投げられます。
- 抽象クラス Entryに実装し、何も行わない。
- addメソッドを Entryクラスに実装するが、何も行わないようにすることもできます。そうすると、Fileクラスからのaddは例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entryで宣言はするが、実装はしない。
- addメソッドを Entryクラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directoryクラスにだけ記述する。
- この例題がそうです。addメソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要なDirectoryクラスにだけ入れるというやり方です。
|
Compositeパターンを使用しない例 Directory.rb require './File'
class Directory
def initialize(name)
@name = name
@list = Array.new()
end
def add(entry)
if @food.is_a?(Filex) or @food.is_a?(Directory) then
@list.push(entry)
end
end
def dir(path)
path += @name + "\\"
@path = path
@list.each do |entry|
entry.dir(@path)
end
end
end
File.js class Filex
def initialize(name, size)
@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("");
|
Compositeパターンを使用した例 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 @food.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("")
|
|
ディレクトリとファイルを考えます。 ファイルを表す Filex クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、Filex をすべて表示します。 よって、Dir メソッドの中で、ファイルなのかディレクトリなのかを判断する必要がありますが、Ruby は変数に型がないので、どんなクラスでもポリモーフィズムができてしまいます。よって、dir メソッドの中で、ファイルなのかディレクトリなのかを判断する必要がありません。 また、オーバーロードする必要もありませんので add メソッドも1つだけですが、意図しないクラスが add されないように、File クラスか Directory クラスかは判定しています。 これでも問題なく動作します。しかし、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。add メソッドでショートカットも許可するようにしなければならなくなります。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、Filex と Directory が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、dir メソッドのみを定義しています。これを実装する形で、Directory クラス、Filex クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、add メソッドが追加されています。また、dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの dir メソッドを呼び出し、そのパスを表示します。インスタンスが Filex ならば、そのインスタンスの dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 Filex クラスは、dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、Filex クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が Filex クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、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クラスにだけ入れるというやり方です。
|
Compositeパターンを使用しない例 Directory.py from File import File
class Directory:
def __init__(self, name):
self.name = name
self.list = []
def add(self, entry):
if isinstance(entry, File) or isinstance(entry, Directory):
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 class File:
def __init__(self, name, size):
self.name = name
self.size = size
def dir(self, path):
print(f"{path}{this.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("")
|
Compositeパターンを使用した例 IEntry.py from abc import ABCMeta, abstractmethod
class IEntry(metaclass=ABCMeta):
@abstractmethod
def dir():
pass
Directory.py from IEntry import IEntry
class Directory(IEntry):
def __init__(self, name):
super().__init__()
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):
super().__init__()
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("")
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、Fileをすべて表示します。 よって、Dir メソッドの中で、ファイルなのかディレクトリなのかを判断する必要がありますが、Python は変数に型がないので、どんなクラスでもポリモーフィズムができてしまいます。よって、dir メソッドの中で、ファイルなのかディレクトリなのかを判断する必要がありません。 また、オーバーロードする必要もありませんので add メソッドも1つだけですが、意図しないクラスが add されないように、File クラスか Directory クラスかは判定しています。 これでも問題なく動作します。しかし、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。add メソッドでショートカットも許可するようにしなければならなくなります。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、File と Directory が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、dir メソッドのみを定義しています。これを実装する形で、Directory クラス、File クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、add メソッドが追加されています。また、dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの dir メソッドを呼び出し、そのパスを表示します。インスタンスが File ならば、そのインスタンスの dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、File クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、IEntry インタフェースを実装した Directoryクラスで addメソッドを定義しています。実際、addメソッドを使えるのが Directoryクラスだけだからです。しかし、 addメソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entryに実装し、例外を発生させる。
- addメソッドを Entryクラスに実装し、例外を発生させます。addメソッドを実際に使える Directoryクラスでは、 Entryクラスのaddメソッドをオーバーライドして、意味のある実装に置き換えます。Fileクラスでは、 Entryクラスのaddメソッドを継承しているのでaddは可能ですが、例外が投げられます。
- 抽象クラス Entryに実装し、何も行わない。
- addメソッドを Entryクラスに実装するが、何も行わないようにすることもできます。そうすると、Fileクラスからのaddは例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entryで宣言はするが、実装はしない。
- addメソッドを Entryクラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directoryクラスにだけ記述する。
- この例題がそうです。addメソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要なDirectoryクラスにだけ入れるというやり方です。
|
Compositeパターンを使用しない例 Directory.php <?php
require_once('File.php');
class Directoryx {
public function __construct($name) {
$this->name = $name;
$this->list = array();
}
public function add($entry) {
if ($entry instanceof File || $entry instanceof Directoryx)
$this->list[] = $entry;
}
public function dir($path) {
$path .= $this->name + "\\";
foreach ($this->list as $entry) {
$entry->dir($path);
}
}
}
?>
File.php <?php
class File {
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("");
?>
|
Compositeパターンを使用した例 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("");
?>
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directoryx クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、Fileをすべて表示します。 よって、Dir メソッドの中で、ファイルなのかディレクトリなのかを判断する必要がありますが、PHP は変数に型がないので、どんなクラスでもポリモーフィズムができてしまいます。よって、dir メソッドの中で、ファイルなのかディレクトリなのかを判断する必要がありません。 また、オーバーロードする必要もありませんので add メソッドも1つだけですが、意図しないクラスが add されないように、File クラスか Directory クラスかは判定しています。 これでも問題なく動作します。しかし、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。add メソッドでショートカットも許可するようにしなければならなくなります。 |
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 クラスにだけ入れるというやり方です。
|
Compositeパターンを使用しない例 Directory.ts import {File} from "./File";
export
class Directory {
private name:string;
private list:Array<File | Directory>;
public constructor(name:string) {
this.name = name;
this.list = new Array<File | Directory>();
}
public add(entry:File | Directory):void {
this.list.add(entry);
}
public dir(path:string):void {
path += this.name + "\\";
this.list.forEach(function(entry:File | Directory) {entry.dir(path);}, this);
}
}
File.ts export
class File {
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("");
|
Compositeパターンを使用した例 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 += 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("");
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、Fileをすべて表示します。 これでも問題なく動作します。しかし、dir メソッドの中で、ファイルなのかディレクトリなのかを判断していますので、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。Directory クラスは、add(Shortcut link) メソッドを追加したり、dir メソッドの中で、 Shortcut に対応できるようにしなければならなくなります。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、File と Directory が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、dir メソッドのみを定義しています。これを実装する形で、Directory クラス、File クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、add メソッドが追加されています。また、dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの dir メソッドを呼び出し、そのパスを表示します。インスタンスが File ならば、そのインスタンスの dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、File クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、IEntry インタフェースを実装した Directoryクラスで addメソッドを定義しています。実際、addメソッドを使えるのが Directoryクラスだけだからです。しかし、 addメソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entryに実装し、例外を発生させる。
- addメソッドを Entryクラスに実装し、例外を発生させます。addメソッドを実際に使える Directoryクラスでは、 Entryクラスのaddメソッドをオーバーライドして、意味のある実装に置き換えます。Fileクラスでは、 Entryクラスのaddメソッドを継承しているのでaddは可能ですが、例外が投げられます。
- 抽象クラス Entryに実装し、何も行わない。
- addメソッドを Entryクラスに実装するが、何も行わないようにすることもできます。そうすると、Fileクラスからのaddは例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entryで宣言はするが、実装はしない。
- addメソッドを Entryクラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directoryクラスにだけ記述する。
- この例題がそうです。addメソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要なDirectoryクラスにだけ入れるというやり方です。ただし、このやり方だと、Directoryのインスタンスが Entry型の変数に入っているときは、 Directory型にキャストしないとaddメソッドが使えないことになります。
|
Compositeパターンを使用しない例 Directory.swift public class Directory {
private var name:String
private var list:[AnyObject] = []
public init(_ name:String) {
self.name = name
}
public func add(_ entry:File) {
list.add(entry)
}
public func add(_ entry:Directory) {
list.add(entry)
}
public func dir(_ path:String) {
let p = path + name + "\\"
list.forEach {
if let file:File = $0 as? File {
file.dir(p)
}
else if let dir:Directory = $0 as? Directory {
dir.dir(p)
}
}
}
}
File.swift public class File {
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("")
|
Compositeパターンを使用した例 IEntry.swift public protocol IEntry {
func 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("")
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、Fileをすべて表示します。 これでも問題なく動作します。しかし、dir メソッドの中で、ファイルなのかディレクトリなのかを判断していますので、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。Directory クラスは、add(Shortcut link) メソッドを追加したり、dir メソッドの中で、 Shortcut に対応できるようにしなければならなくなります。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、File と Directory が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、dir メソッドのみを定義しています。これを実装する形で、Directory クラス、File クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、add メソッドが追加されています。また、dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの dir メソッドを呼び出し、そのパスを表示します。インスタンスが File ならば、そのインスタンスの dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、File クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、IEntry インタフェースを実装した Directoryクラスで addメソッドを定義しています。実際、addメソッドを使えるのが Directoryクラスだけだからです。しかし、 addメソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entryに実装し、例外を発生させる。
- addメソッドを Entryクラスに実装し、例外を発生させます。addメソッドを実際に使える Directoryクラスでは、 Entryクラスのaddメソッドをオーバーライドして、意味のある実装に置き換えます。Fileクラスでは、 Entryクラスのaddメソッドを継承しているのでaddは可能ですが、例外が投げられます。
- 抽象クラス Entryに実装し、何も行わない。
- addメソッドを Entryクラスに実装するが、何も行わないようにすることもできます。そうすると、Fileクラスからのaddは例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entryで宣言はするが、実装はしない。
- addメソッドを Entryクラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directoryクラスにだけ記述する。
- この例題がそうです。addメソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要なDirectoryクラスにだけ入れるというやり方です。ただし、このやり方だと、Directoryのインスタンスが Entry型の変数に入っているときは、 Directory型にキャストしないとaddメソッドが使えないことになります。
|
Compositeパターンを使用しない例 Directory.kt class Directory(private val name: String) {
private var list: MutableList<Any> = ArrayList()
fun add(file: File) {
list.add(file)
}
fun add(dir: Directory) {
list.add(dir)
}
fun dir(path: String) {
var path = path
path += name + "\\"
val it: Iterator<Any> = list.iterator()
while (it.hasNext()) {
val obj = it.next()
if (obj is File) {
obj.dir(path)
}
else if (obj is Directory) {
obj.dir(path)
}
}
}
}
File.kt class File(private val name: String, val size: Int) {
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("")
}
|
Compositeパターンを使用した例 IEntry.kt interface IEntry {
fun dir(path: String)
}
Directory.kt class Directory(private val name: String) : IEntry {
private var list: MutableList<IEntry> = ArrayList()
fun add(entry:IEntry) {
list.add(entry)
}
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, 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("")
}
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、Fileをすべて表示します。 これでも問題なく動作します。しかし、dir メソッドの中で、ファイルなのかディレクトリなのかを判断していますので、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。Directory クラスは、add(Shortcut link) メソッドを追加したり、dir メソッドの中で、 Shortcut に対応できるようにしなければならなくなります。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、File と Directory が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、dir メソッドのみを定義しています。これを実装する形で、Directory クラス、File クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、add メソッドが追加されています。また、dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの dir メソッドを呼び出し、そのパスを表示します。インスタンスが File ならば、そのインスタンスの dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、File クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、IEntry インタフェースを実装した Directoryクラスで addメソッドを定義しています。実際、addメソッドを使えるのが Directoryクラスだけだからです。しかし、 addメソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entryに実装し、例外を発生させる。
- addメソッドを Entryクラスに実装し、例外を発生させます。addメソッドを実際に使える Directoryクラスでは、 Entryクラスのaddメソッドをオーバーライドして、意味のある実装に置き換えます。Fileクラスでは、 Entryクラスのaddメソッドを継承しているのでaddは可能ですが、例外が投げられます。
- 抽象クラス Entryに実装し、何も行わない。
- addメソッドを Entryクラスに実装するが、何も行わないようにすることもできます。そうすると、Fileクラスからのaddは例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entryで宣言はするが、実装はしない。
- addメソッドを Entryクラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directoryクラスにだけ記述する。
- この例題がそうです。addメソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要なDirectoryクラスにだけ入れるというやり方です。ただし、このやり方だと、Directoryのインスタンスが Entry型の変数に入っているときは、 Directory型にキャストしないとaddメソッドが使えないことになります。
|
Compositeパターンを使用しない例 Directory.scala class Directory(val name: String) {
private var list = new ArrayList[Object]()
def add(file: File) {
list.add(file)
}
def add(dir: Directory) {
list.add(dir)
}
def dir(path: String) {
var p = path + name + "\\"
val it = list.iterator()
while (it.hasNext) {
val obj = it.next
if (obj.isInstanceOf[File]) {
obj.asInstanceOf[File].dir(p)
}
else if (obj.isInstanceOf[Directory]) {
obj.asInstanceOf[Directory].dir(p)
}
}
}
}
File.scala class File(val name: String, val size: Int) {
def 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("")
}
}
|
Compositeパターンを使用した例 IEntry.scala trait IEntry {
def dir(path: String)
}
Directory.scala import java.util.ArrayList
class Directory(val name: String) extends IEntry {
private var list = new ArrayList[IEntry]()
def add(entry:IEntry) {
list.add(entry)
}
def dir(path: String) {
val p = path + name + "\\"
val it = list.iterator()
while (it.hasNext) {
val entry: IEntry = it.next
entry.dir(p)
}
}
File.scala class File(val name: String, size: Int) extends IEntry {
def 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("")
}
}
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、Fileをすべて表示します。 これでも問題なく動作します。しかし、dir メソッドの中で、ファイルなのかディレクトリなのかを判断していますので、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。Directory クラスは、add(Shortcut link) メソッドを追加したり、dir メソッドの中で、 Shortcut に対応できるようにしなければならなくなります。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、File と Directory が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、dir メソッドのみを定義しています。これを実装する形で、Directory クラス、File クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、add メソッドが追加されています。また、dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの dir メソッドを呼び出し、そのパスを表示します。インスタンスが File ならば、そのインスタンスの dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、File クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、IEntry インタフェースを実装した Directoryクラスで addメソッドを定義しています。実際、addメソッドを使えるのが Directoryクラスだけだからです。しかし、 addメソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entryに実装し、例外を発生させる。
- addメソッドを Entryクラスに実装し、例外を発生させます。addメソッドを実際に使える Directoryクラスでは、 Entryクラスのaddメソッドをオーバーライドして、意味のある実装に置き換えます。Fileクラスでは、 Entryクラスのaddメソッドを継承しているのでaddは可能ですが、例外が投げられます。
- 抽象クラス Entryに実装し、何も行わない。
- addメソッドを Entryクラスに実装するが、何も行わないようにすることもできます。そうすると、Fileクラスからのaddは例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entryで宣言はするが、実装はしない。
- addメソッドを Entryクラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directoryクラスにだけ記述する。
- この例題がそうです。addメソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要なDirectoryクラスにだけ入れるというやり方です。ただし、このやり方だと、Directoryのインスタンスが Entry型の変数に入っているときは、 Directory型にキャストしないとaddメソッドが使えないことになります。
|
Compositeパターンを使用しない例 Directory.groovy class Directory {
private List<Object> list = null
private String name = null
Directory(String name) {
this.name = name
list = new ArrayList<Object>()
}
void add(File file) {
list.add(file)
}
void add(Directory dir) {
list.add(dir)
}
void dir(String path) {
path += name + "\\"
Iterator<Object> it = list.iterator()
while (it.hasNext()) {
Object obj = it.next()
if (obj instanceof File) {
((File) obj).dir(path)
}
else if (obj instanceof Directory) {
((Directory) obj).dir(path)
}
}
}
}
File.groovy class File {
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 {
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("")
}
}
|
Compositeパターンを使用した例 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 {
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("")
}
}
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、Fileをすべて表示します。 これでも問題なく動作します。しかし、dir メソッドの中で、ファイルなのかディレクトリなのかを判断していますので、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。Directory クラスは、add(Shortcut link) メソッドを追加したり、dir メソッドの中で、 Shortcut に対応できるようにしなければならなくなります。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、File と Directory が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、dir メソッドのみを定義しています。これを実装する形で、Directory クラス、File クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、add メソッドが追加されています。また、dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの dir メソッドを呼び出し、そのパスを表示します。インスタンスが File ならば、そのインスタンスの dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、File クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、IEntry インタフェースを実装した Directoryクラスで addメソッドを定義しています。実際、addメソッドを使えるのが Directoryクラスだけだからです。しかし、 addメソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entryに実装し、例外を発生させる。
- addメソッドを Entryクラスに実装し、例外を発生させます。addメソッドを実際に使える Directoryクラスでは、 Entryクラスのaddメソッドをオーバーライドして、意味のある実装に置き換えます。Fileクラスでは、 Entryクラスのaddメソッドを継承しているのでaddは可能ですが、例外が投げられます。
- 抽象クラス Entryに実装し、何も行わない。
- addメソッドを Entryクラスに実装するが、何も行わないようにすることもできます。そうすると、Fileクラスからのaddは例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entryで宣言はするが、実装はしない。
- addメソッドを Entryクラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directoryクラスにだけ記述する。
- この例題がそうです。addメソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要なDirectoryクラスにだけ入れるというやり方です。ただし、このやり方だと、Directoryのインスタンスが Entry型の変数に入っているときは、 Directory型にキャストしないとaddメソッドが使えないことになります。
|
Compositeパターンを使用しない例 Directory.go import (
"reflect"
"strings"
)
type Directory struct {
list []interface{}
name string
}
func (self *Directory) Add(entry interface{}) {
self.list = append(self.list, entry)
}
func (self *Directory) Dir(path string) {
path += self.name + "\\"
for _, entry := range self.list {
var t = reflect.ValueOf(entry).Type().String() // ValueOfでreflect.Value型のオブジェクトを取得
var n = strings.LastIndex(t, ".")
var e = t[n+1:]
if (e == "File") {
entry).(*File).Dir(path)
} else if (e == "Directory") {
entry).(*Directory).Dir(path)
}
}
}
func NewDirectory(name string) *Directory {
return &Directory {
name: name,
}
}
File.go import "fmt"
type File struct {
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) {
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("")
}
|
Compositeパターンを使用した例 IEntry.go type IEntry interface {
Dir(path 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) *Directory {
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("")
}
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、Fileをすべて表示します。 これでも問題なく動作します。しかし、dir メソッドの中で、ファイルなのかディレクトリなのかを判断していますので、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。Directory クラスは、add(Shortcut link) メソッドを追加したり、dir メソッドの中で、 Shortcut に対応できるようにしなければならなくなります。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、File と Directory が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、dir メソッドのみを定義しています。これを実装する形で、Directory クラス、File クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、add メソッドが追加されています。また、dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの dir メソッドを呼び出し、そのパスを表示します。インスタンスが File ならば、そのインスタンスの dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、File クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、IEntry インタフェースを実装した Directoryクラスで addメソッドを定義しています。実際、addメソッドを使えるのが Directoryクラスだけだからです。しかし、 addメソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entryに実装し、例外を発生させる。
- addメソッドを Entryクラスに実装し、例外を発生させます。addメソッドを実際に使える Directoryクラスでは、 Entryクラスのaddメソッドをオーバーライドして、意味のある実装に置き換えます。Fileクラスでは、 Entryクラスのaddメソッドを継承しているのでaddは可能ですが、例外が投げられます。
- 抽象クラス Entryに実装し、何も行わない。
- addメソッドを Entryクラスに実装するが、何も行わないようにすることもできます。そうすると、Fileクラスからのaddは例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entryで宣言はするが、実装はしない。
- addメソッドを Entryクラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directoryクラスにだけ記述する。
- この例題がそうです。addメソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要なDirectoryクラスにだけ入れるというやり方です。ただし、このやり方だと、Directoryのインスタンスが Entry型の変数に入っているときは、 Directory型にキャストしないとaddメソッドが使えないことになります。
|
Compositeパターンを使用しない例 directory.d import file;
public class Directory {
private Object[] list;
private string name = null;
public this(in string name) {
this.name = name;
}
public void add(File file) {
list ~= file;
}
public void add(Directory dir) {
list ~= dir;
}
public void dir(string path) {
path ~= name ~ "\\";
foreach (Object obj; list) {
if (cast(File) obj) {
(cast(File) obj).dir(path);
}
else if (cast(Directory) obj) {
(cast(Directory) obj).dir(path);
}
}
}
}
file.d import std.stdio;
public class File {
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 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;
}
|
Compositeパターンを使用した例 ientry.d public interface IEntry {
void dir(string path);
}
directory.d import ientry;
public class Directory : IEntry {
private IEntry[] list;
private string name = null;
public this(in 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 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;
}
|
|
ディレクトリとファイルを考えます。 ファイルを表す File クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、Fileをすべて表示します。 これでも問題なく動作します。しかし、dir メソッドの中で、ファイルなのかディレクトリなのかを判断していますので、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。Directory クラスは、add(Shortcut link) メソッドを追加したり、dir メソッドの中で、 Shortcut に対応できるようにしなければならなくなります。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、File と Directory が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、dir メソッドのみを定義しています。これを実装する形で、Directory クラス、File クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、add メソッドが追加されています。また、dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの dir メソッドを呼び出し、そのパスを表示します。インスタンスが File ならば、そのインスタンスの dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、File クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、IEntry インタフェースを実装した Directoryクラスで addメソッドを定義しています。実際、addメソッドを使えるのが Directoryクラスだけだからです。しかし、 addメソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entryに実装し、例外を発生させる。
- addメソッドを Entryクラスに実装し、例外を発生させます。addメソッドを実際に使える Directoryクラスでは、 Entryクラスのaddメソッドをオーバーライドして、意味のある実装に置き換えます。Fileクラスでは、 Entryクラスのaddメソッドを継承しているのでaddは可能ですが、例外が投げられます。
- 抽象クラス Entryに実装し、何も行わない。
- addメソッドを Entryクラスに実装するが、何も行わないようにすることもできます。そうすると、Fileクラスからのaddは例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entryで宣言はするが、実装はしない。
- addメソッドを Entryクラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directoryクラスにだけ記述する。
- この例題がそうです。addメソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要なDirectoryクラスにだけ入れるというやり方です。ただし、このやり方だと、Directoryのインスタンスが Entry型の変数に入っているときは、 Directory型にキャストしないとaddメソッドが使えないことになります。
|
Compositeパターンを使用しない例 Directory.pas unit UnitDirectory;
interface
uses
System.Generics.Collections,
UnitFile;
type
Directory = class
private
var list:TList<TObject>;
var name:string;
public
constructor Create(name:string);
destructor Destroy(); override;
procedure add(_file:Filex); overload;
procedure add(dir:Directory); overload;
procedure dir(path:string);
end;
implementation
constructor Directory.Create(name:string);
begin
self.name := name;
list := TList<TObject>.Create();
end;
destructor Directory.Destroy();
var obj:TObject;
begin
for obj in list do begin
obj.Free;
end;
list.Free;
end;
procedure Directory.add(_file:Filex);
begin
list.Add(_file);
end;
procedure Directory.add(dir:Directory);
begin
list.Add(dir);
end;
procedure Directory.dir(path:string);
var obj:TObject;
begin
path := path + name + '\';
for obj in list do begin
if obj is Filex then begin
(obj as Filex).dir(path);
end
else begin
(obj as Directory).dir(path);
end;
end;
end;
end.
File.pas unit UnitFile;
interface
uses
System.SysUtils;
type
Filex = class
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.
|
Compositeパターンを使用した例 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.
|
|
ディレクトリとファイルを考えます。 ファイルを表す Filex クラスは、インスタンス変数 name と size を持ちます。また、dir メソッドが呼ばれると、「ファイル名(サイズ)」と表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、dir メソッドが呼ばれた場合には、Filex をすべて表示します。 これでも問題なく動作します。しかし、dir メソッドの中で、ファイルなのかディレクトリなのかを判断していますので、もしここで、「ディレクトリには、ディレクトリとファイルだけでなくショートカットも入るようにしたい」という要求が出てきたらどうでしょう。Directory クラスは、add(Shortcut link) メソッドを追加したり、dir メソッドの中で、 Shortcut に対応できるようにしなければならなくなります。 |
Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。ここでは、Filex と Directory が共通のインタフェース IEntry を実装しています。 IEntry インタフェースでは、dir メソッドのみを定義しています。これを実装する形で、Directory クラス、File クラスを作成すると、例のようになります。 Directory クラスは、内部にディレクトリやファイルを持てますので、add メソッドが追加されています。また、dir メソッドは、パスを表示します。そして、インスタンスが Directory ならば、さらにそのインスタンスの dir メソッドを呼び出し、そのパスを表示します。インスタンスが Filex ならば、そのインスタンスの dir メソッドを呼び出し、ファイル名とサイズを表示します。共通のインタフェースである IEntry から呼び出すことによってポリモーフィズムが利用されています。 File クラスは、dir メソッドがファイル名とサイズを表示します。 このように、Directory クラス、Filex クラスを共に IEntry インタフェースを実装するクラスとすることで、 Directory クラスの dir メソッド内では、実態が Filex クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも IEntry インスタンスとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。 もしここで、 Shortcut クラスを追加する必要が生じたとしも、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、IEntry インタフェースを実装するように、 Shortcut クラスを実装すればよいのです。 |
この例では、IEntry インタフェースを実装した Directoryクラスで addメソッドを定義しています。実際、addメソッドを使えるのが Directoryクラスだけだからです。しかし、 addメソッドの置き方、実装の仕方にはいろいろな場合があります。
- 抽象クラス Entryに実装し、例外を発生させる。
- addメソッドを Entryクラスに実装し、例外を発生させます。addメソッドを実際に使える Directoryクラスでは、 Entryクラスのaddメソッドをオーバーライドして、意味のある実装に置き換えます。Fileクラスでは、 Entryクラスのaddメソッドを継承しているのでaddは可能ですが、例外が投げられます。
- 抽象クラス Entryに実装し、何も行わない。
- addメソッドを Entryクラスに実装するが、何も行わないようにすることもできます。そうすると、Fileクラスからのaddは例外も発生しないかわりに、何も起きないということになります。
- 抽象クラス Entryで宣言はするが、実装はしない。
- addメソッドを Entryクラスでは抽象メソッドとしておき、サブクラスでは、必要ならば意味のある実装を定義し、必要でないならば例外を発生させたり、何もしないという実装にします。ただし、本来そのサブクラスでは不要なメソッドをも記述しなければならなくなります。
- Directoryクラスにだけ記述する。
- この例題がそうです。addメソッドを 抽象クラス Entry(この例では IEntry インタフェース)には入れずに、本当に必要なDirectoryクラスにだけ入れるというやり方です。ただし、このやり方だと、Directoryのインスタンスが Entry型の変数に入っているときは、 Directory型にキャストしないとaddメソッドが使えないことになります。