メールの詳細(トピック表示)
Delphi によるサンプルの実装 『 Iteratorパターン 』
投稿者: さん 2002/03/19 14:58 MLNo.1414 [メール表示]
こちらには初めて投稿します。ひらぽん@エース設計と申します。
現在、結城さんの本と Gofと さらにブリシデスの書いた
「パターンハッチング」を並べて、デザパタの勉強中の身です。
ただし読んでてどうにも苦しいのが・・・
普段 Delphi を使ってるだけに、言語仕様の違いで、
JAVA やC++ の例をどうDelphiに実装するか・・・
頭を痛めるケースが多々あったりします。
で、どうせなら各サンプルをDelphiで実装しなおしたものを
後学の為にも、このMLおよび、Delphi-MLで公開したいと考えてるのですが
果たしてそれって許されるのでしょうか。(^^;
ちなみに『 Iterator 』パターンのサンプルをDelphiで実装したら
こうなりました。(^^;
クラス・インターフェイス・フィールドの名前はDelphiでの慣例に従い
クラス名には「T」、インターフェイスには「I」、
フィールドには「F」を先頭に付けてます。
ソースは突っ込み大歓迎です。(^^;
// Main.dpr ========================================================
program Main;
{$APPTYPE CONSOLE}
uses
Iterator in 'Iterator.pas';
var
bookShelf: TBookShelf;
book: TBook;
it: IIterator;
begin
bookShelf := TBookShelf.Create;
bookShelf.appendBook( TBook.Create( '徳川家康' ) );
bookShelf.appendBook( TBook.Create( '燃えよ剣' ) );
bookShelf.appendBook( TBook.Create( '坂の上の雲' ) );
bookShelf.appendBook( TBook.Create( '写真 太平洋戦争' ) );
bookShelf.appendBook( TBook.Create( '小説吉田学校' ) );
it := bookShelf.iterator;
while ( it.hasNext() ) do
begin
book := TBook( it.next() );
Writeln( book.getName );
book.Free;
end;
bookShelf.Free;
Readln;
end.
// Iterator.pas ====================================================
unit Iterator;
interface
uses
classes;
{ Aggreagte インターフェイス }
type
IAggreagte = interface
['{A3F20B7C-B48C-4D44-927D-840A6C0DBFC8}']
function Iterator: IIterator;
end;
{ Iterator インターフェイス }
type
IIterator = interface
['{26892D0A-E2F3-4767-8E39-C28FF20F55C2}']
function hasNext: Boolean;
function next: TObject;
end;
{ Book クラス }
type
TBook = class( TObject )
private
Fname: string;
public
constructor Create( name: string );
function getName: string;
end;
{ BookShelf クラス }
type
TBookShelf = class( TInterfacedObject, IAggreagte )
private
Fbooks: TStringList;
public
constructor Create;
destructor Destroy; override;
function getBookAt( index: integer ): TBook;
procedure appendBook( book: TBook );
function getLength: integer;
function iterator: IIterator;
end;
{ BookShelfIterator クラス }
type
TBookShelfIterator = class( TInterfacedObject, IIterator )
private
FbookShelf: TBookShelf;
Findex: integer;
public
constructor Create( const bookshelf: TBookShelf );
function hasNext: Boolean;
function next: TObject;
end;
implementation
{ TBook }
constructor TBook.Create( name: string );
begin
Fname := name;
end;
function TBook.getName: string;
begin
Result := Fname;
end;
{ TBookShelf }
procedure TBookShelf.appendBook( book: TBook );
begin
Fbooks.AddObject( '', book );
end;
constructor TBookShelf.Create;
begin
Fbooks := TStringList.Create;
end;
destructor TBookShelf.Destroy;
begin
Fbooks.Free;
inherited;
end;
function TBookShelf.getBookAt(index: integer): TBook;
begin
Result := TBook( Fbooks.Objects[ index ] );
end;
function TBookShelf.getLength: integer;
begin
Result := Fbooks.Count;
end;
function TBookShelf.iterator: IIterator;
begin
Result := TBookShelfIterator.Create( Self );
end;
{ TBookShelfIterator }
constructor TBookShelfIterator.Create( const bookshelf: TBookShelf );
begin
Fbookshelf := bookshelf;
Findex := 0;
end;
function TBookShelfIterator.hasNext: Boolean;
begin
if ( Findex < FbookShelf.getLength() ) then
Result := True
else
Result := False;
end;
function TBookShelfIterator.next: TObject;
begin
Result := FbookShelf.getBookAt( Findex );
Inc( Findex );
end;
end.
////////////////////////////////////////////////////////////
有限会社 エース設計
Mail ace@…
Home Page http://www.acesekkei.com/
////////////////////////////////////////////////////////////
読み込み中...-
MLNo.1423
Himuro UTOさん
(0) 2002/03/20 12:59 [メール表示する]

こんにちは、ひむろ と申します
ひらぽんさん の Delphi 情報、たいへんありがたいです。
珍しく Delphi についての話だったのでついつい反応してしまいました。
個人的には Ruby とか Java の方が得意だったのですが、
仕事柄 Delphi に手を染めざるを得なくなり、最近は
Delphi ばっかし使っております。
しかし Delphi って RAD ツールであることから、
デザインパターン やら アルゴリズム・データ構造についての
日本語での情報は少なく、ひらぽんさんがお書きになったような
情報というのは非常に貴重でありがたく思いました。
> > ふと思ったのですが、DelphiにはIteratorの役割を果たす
> > 標準クラス(ライブラリ?)はないのでしょうか。
>
> ないのです〜。。。(T^T)
> List 関連なら豊富にあるのですが・・・
ほんとコレは困りものですよね。
Java や STL が非常に恋しい...
TList 等にしても TObject から派生したものは
格納できても、Integer 等のプリミティブな型は
格納できない。
(Java の int -> Integer みたいなラッパーがないし)
ハッシュにしても [Key]String -> [Value]TObject
のハッシュしか用意されていない。
Delphian の皆さんは自作して対処しているのだろうか?
おっと デザインパターンの話題でしたので
とりあえず Delphi についての Design Pattern の情報
のリンクを紹介しておきます。(英語)
* Delphi Design Pattern
http://www.obsof.com/delphi_tips/pattern.html
次のやつは C++ の STL, Java の JGL に当たるものを開発しようと
いう試みみたいです(デザインパターンという意味では Iterator に関連がある
というくらいで、本来はアルゴリズムとデータ構造の情報ですね)
* https://sourceforge.net/projects/decal/
--
==================================================
ひむろ うと
himuro-uto@…
==================================================

-
MLNo.1424
さん
(0) 2002/03/20 13:10 [メール表示する]


ひらぽんです。
> ひらぽんさん の Delphi 情報、たいへんありがたいです。
> 珍しく Delphi についての話だったのでついつい反応してしまいました。
反応していただき、有難う御座います。m(_ _)m
Delphi-MLの方にも流しておりますが、向こうは大変な事に
なりつつあるようです。
言語仕様やら何やら、まぁ色々と・・・(^^;
Delphi メーリングリスト
http://www.users.gr.jp/ml/delphi.aspx
ただ過去ログにUPされるのは時間がかかるようです。
#しかし、FreeML の検索システムはあまりよろしくないようで。(^^;
////////////////////////////////////////////////////////////
有限会社 エース設計
Mail ace@…
Home Page http://www.acesekkei.com/
////////////////////////////////////////////////////////////

-
MLNo.1425
さん
(0) 2002/03/23 14:37 [メール表示する]


初めまして、陽の字といいます。
練習のつもりでちょっとしたクラスライブラリを設計していたのですが、ひとつ判断
しかねるところがあったのでみなさんの意見を聞かせてください。
まず、以下の条件を考えます。
1・setもしくはgetなどの関数を持つ単純な抽象クラス「MyData」を定義した。
この「MyData」から抽象でないクラス「DogData」「CatData」「MonkeyData]などを
派生する(要はデータ型である)。
2・「MyData」をファイルに出力するための抽象クラス「MyWriter」を定義した。
この「MyWriter」から抽象でないクラス「XMLWriter」「CSVWriter」
「BinaryWriter]
などを派生する(とどのつまり、ファイルIOのツールクラス)。
3・「MyData」の派生クラスのインスタンスを、クラス「MyApp」のフィールドであ
る配列
'dataArray'に複数個登録してある。
4・「MyWriter」の派生クラスのインスタンスを、クラス「MyApp」のフィールド
'writer'に
登録してある。
さて、これらのクラスを組み合わせて「MyData」の内容をファイルに順に出力するこ
とを
考えてみます。
ふたつの実装方法が考えられると思います。
方法A
abstract public class MyData {
…
// 派生クラスで実装
abstract void writeFile(MyWriter writer);
…
}
public class MyApp {
…
void funcA() {
for (int i = 0; i < dataArray.length; i++) {
dataArray[i].writeFile(writer);
}
}
…
}
方法B
abstract public class MyWriter {
…
// 派生クラスで実装
abstract void process(MyData data);
…
}
public class MyApp {
…
void funcB() {
for (int i = 0; i < dataArray.length; i++) {
writer.process(dataArray[i]);
}
}
…
}
一応どちらでもTemplateMethodのやり方には則ってはいる、というのがポイントで
す。
クラス間の依存関係やライブラリの他の部分との兼ね合いもあるのでしょうが、
方法AとBでは、どちらの方法がベターでしょうか。
もしFAQレベルの質問だったらごめんなさい。

-
MLNo.1426
井芹さん
(0) 2002/03/23 15:33 [メール表示する]

こんにちは、井芹と申します。
すごく久しぶりにポストしてみます。
#的確な返信が出る前に、自分なりにない頭を絞ってみました。
#じゃないと、いつものごとく納得して終わってしまうので・・・
--- Yohichiro Hattoriからのメッセージ:
> 方法A
> abstract public class MyData {
> …
> // 派生クラスで実装
> abstract void writeFile(MyWriter writer);
> …
> }
>
> public class MyApp {
> …
> void funcA() {
> for (int i = 0; i < dataArray.length; i++) {
> dataArray[i].writeFile(writer);
> }
> }
> …
> }
>
> 方法B
> abstract public class MyWriter {
> …
> // 派生クラスで実装
> abstract void process(MyData data);
> …
> }
>
> public class MyApp {
> …
> void funcB() {
> for (int i = 0; i < dataArray.length; i++) {
> writer.process(dataArray[i]);
> }
> }
> …
> }
>
> 一応どちらでもTemplateMethodのやり方には則ってはいる、というのがポイン
トで
> す。
> クラス間の依存関係やライブラリの他の部分との兼ね合いもあるのでしょうが 、
> 方法AとBでは、どちらの方法がベターでしょうか。
Bの方が良いと思います。
ファイルはwriterがなくても存在の意味がありますが、writerは
その出力対象となるファイルを知らないと意味がないですから。
ドキュメントはwriterについて知る必要はないが、writerは対象
ファイルを教えてもらえないと何もできないということで。
__________________________________________________
Do You Yahoo!?
Yahoo! BB is Broadband by Yahoo! http://bb.yahoo.co.jp/

-
MLNo.1427
さん
(0) 2002/03/24 01:09 [メール表示する]


ども、皆川@(株)豆蔵と申します。
# すいません m(_ _)m ちょっと長くなってしまいました。
# 出張の合間の息抜きのハズだったのに… (^_^;
On Sat, 23 Mar 2002 14:41:35 +0900
"Yohichiro Hattori"wrote:
> 初めまして、陽の字といいます。
>
> 練習のつもりでちょっとしたクラスライブラリを設計していたのですが、ひとつ判断
> しかねるところがあったのでみなさんの意見を聞かせてください。
>
(中略)
>
> 一応どちらでもTemplateMethodのやり方には則ってはいる、というのがポイントで
> す。
> クラス間の依存関係やライブラリの他の部分との兼ね合いもあるのでしょうが、
> 方法AとBでは、どちらの方法がベターでしょうか。
> もしFAQレベルの質問だったらごめんなさい。
これはなかなか悩ましいところですよね。
【方法A】 MyData#writeFile(MyWriter)
利点: 書き出し方はデータ側で制御できる(データの種類に応じて変えられる)。
欠点: Writerの種類に応じて書き出し方を変える必要がある場合#writeFile()
メソッドのコードに手が入る(Writerの具象クラスを判別するif文が入
るため)。
【方法B】 MyWriter#process(MyData)
利点: 書き出し方はWriter側で制御できる(Writerの種類に応じて変えられる)。
欠点: Writerはデータの内容を知らなければ書き出すことができない。データ
の種類が増えた場合に#process()メソッドのコードに手が入る。
どちらかというと「方法A」の方が(まだ)許容できる範囲の欠点(書き出しに抽
象クラスMyWriterのインタフェースのみを用いるのであれば問題ない)のような
気がしますが、Writerの種類毎にロジックを切り替える必要が生じた場合、どち
らも五十歩百歩というところでしょうか…。
…要は、「二つの(基本的に)別々のクラス階層に属するクラス群の組み合わせ
によって実行するアルゴリズムが変わってくる」というケースのように思えます。
このような場合によく用いられるのが "Double Dispatch" と呼ばれるコーディ
ング・テクニックで、以下のページに簡単な解説記事がありますので、もしよろ
しければご参照くださいませ。
http://www.mamezou.com/tec/Tips/doubleDispatch/index.html
今回の例で Double Dispatch を構成するとしたら、おそらく以下のような感
じになるでしょう。
------------------------------------------------------------------------
// MyWriter側の階層
abstract public class MyWriter {
public abstract void process( MyData data ) ;
}
public class XMLWriter extends MyWriter {
public void process( MyData data ) {
data.writeFileXML( this ) ;
}
}
public class CSVWriter extends MyWriter {
public void process( MyData data ) {
data.writeFileCSV( this ) ;
}
}
public class BinaryWriter extends MyWriter {
public void process( MyData data ) {
data.writeFileBinary( this ) ;
}
}
// MyData側の階層
abstract public class MyData {
public void writeFile( MyWriter writer ) {
writer.process( this ) ;
}
abstract public void writeFileXML( XMLWriter writer ) ;
abstract public void writeFileCSV( CSVWriter writer ) ;
abstract public void writeFileBinary( BinaryWriter writer ) ;
}
public class DogData extends MyData {
public void writeFileXML( XMLWriter writer ) {
// DogDataをXMLWriterで書き出すロジック
}
public void writeFileCSV( CSVWriter writer ) {
// DogDataをCSVWriterで書き出すロジック
}
public void writeFileBinary( BinaryWriter writer ) {
// DogDataをBinaryWriterで書き出すロジック
}
}
// …以下、同様にCatData,MonkeyDataなどを実装。
------------------------------------------------------------------------
このようなコーディングだと、呼び出し側は MyData#writeFile(MyWriter) で
も MyWriter#process(MyData) でも、どちらでもOKです(両方の呼び出し形式
をサポートしている)。
「大抵の場合は抽象的なWriterとして扱えれば十分だが、特定のWriterと特定
のDataの組み合わせの際だけ特殊なロジックで処理しなければならない」という
ような場合、上記抽象クラス MyData の定義を以下のようにしておくと、各サブ
クラスでイチイチ #writerFileXXX() メソッドをオーバーライドする必要がなく
なってコーディング量を抑えることができそうです。
------------------------------------------------------------------------
abstract public class MyData {
public void writeFile( MyWriter writer ) {
writer.process( this ) ;
}
public void writeFileXML( XMLWriter writer ) {
this.writeFileGeneric( writer ) ;
}
public void writeFileCSV( CSVWriter writer ) {
this.writeFileGeneric( writer ) ;
}
public void writeFileBinary( BinaryWriter writer ) {
this.writeFileGeneric( writer ) ;
}
abstract protected void writeFileGeneric( MyWriter writer ) ;
}
------------------------------------------------------------------------
Javaをはじめとするメソッド・オーバーロードをサポートしている言語であれ
ば、各#writeFileXXX()メソッドは#writeFile()という同じ名前で統一すること
もできるでしょう。
言うまでも無く、このコードでは将来Writerの種類が増えた場合にMyDataの
#writeFileXXX()メソッドが追加されなくてはならないというのが欠点です。で
すが、メソッド単位で追加されるだけで、既存のメソッドのコードには手が入ら
ないというのがミソです。逆に、MyDataのサブクラスが追加される場合は、これ
は単にクラスを追加するだけで既存のコードにはいっさい手が入りません。もし、
Writerの種類が多い、あるいは、将来Writerのサブクラスの方が沢山追加される
可能性が高い…というのであれば、Dispatchの方向を逆転させたコーディングを
しておくのが有利でしょう。
以上、何らかのお役に立ちましたら幸いです。
--
/|/| ▲ 皆川 誠 @ 株式会社 豆蔵
/ ノ / } 「きつねはコンと鳴く」
|// ノ / | 会社用: kitsune@…
=o=|\| / 自宅用: kitsune-san@…
く | / 携帯用: kitsunex@…
■■■■

-
MLNo.1428
さん
(0) 2002/03/24 09:26 [メール表示する]

高橋征義です。
あんまり本題とは関係ないのですが。
"Yohichiro Hattori"wrote:
> 一応どちらでもTemplateMethodのやり方には則ってはいる、というのが
> ポイントです。
結城さんのパターン入門本では、全編を通して、各パターンで使われている
クラスのことを「登場人物」「〜の役」という言葉で表しています。
これはこの本の分かりやすさにかなり貢献している、うまい説明の仕方だと
思います。
# さすがですねえ。
で、Template Methodパターンの登場人物はp.39にありますが、陽の字さん
の例の場合、
・AbstractClassの役
・その中の、テンプレートメソッド
・テンプレートメソッドで使う抽象メソッド
・ConcreteClassの役
・テンプレートメソッドから呼び出されるメソッドの実装
はそれぞれ何になるんでしょう? それぞれの役をどれかのクラスが
ちゃんと演じてますか? 何か足りないような気がしません?
……ということで、いまいち(GoF本で言うところの)Template Methodの
ことでの質問にはなってないんじゃないかなあ、と思うのですがいかが
でしょう(^^;
参考までに、GoF本でのTemplate Methodパターンの目的を書いておきます。
「1つのオペレーションにアルゴリズムのスケルトンを定義しておき、
その中のいくつかのステップについては、サブクラスでの定義に
任せることにする。Template Methodパターンでは、アルゴリズム
の構造を変えずに、アルゴリズム中のあるステップをサブクラスで
再定義する。」
# なお、ここはダブルディスパッチを適用するべき場面では、という
# [DP/ML:1427]の皆川さんの意見には同意です。
高橋征義 (TAKAHASHI Masayoshi) E-mail: maki@…
AbstractClassのconcreteメソッドが縮退?してabstract methodと
一体になってしまった、という見方については、「スケルトン」を
AbstractClass側で実装できる、というTemplate Methodパターンの
旨味がなくなってしまうので、ちと苦しいと思うのです。

-
MLNo.1429
OZAWA -Crouton- Sakuroさん
(0) 2002/03/24 16:44 [メール表示する]

さくです。
In article <20020324091601X.maki@…>,
TAKAHASHI Masayoshiwrites:
> ……ということで、いまいち(GoF本で言うところの)Template Methodの
> ことでの質問にはなってないんじゃないかなあ、と思うのですがいかが
> でしょう(^^;
質問を見た瞬間に条件反射的に「Visitorパターンじゃないのか、これ?」
と思って *見ていました* (汗)
一連のデータ構造(継承関係があるのかもしれないし、内部にツリー構造を
持ったCompositeパターンなのかもしれない)に対して、いろいろなオペレー
ションが施される、という状況ですから、オペレーション(Writer)をVisitor
として設計するとうまくいきそうな気がします。
> # なお、ここはダブルディスパッチを適用するべき場面では、という
> # [DP/ML:1427]の皆川さんの意見には同意です。
ダブルディスパッチもVisitorパターンと関連の深いキーワードですね。
--
OZAWA -Crouton- Sakuro VERBA VOLANT, SCRIPTA MANENT
Mail: mailto:crouton@… GnuPG: 1C1A 4C26 32E2 A911 7B62
Web: http://www.weatherlight.org/~crouton/ E194 37C0 8725 F1D8 F388
** Changed 2001/11/09 **

-
MLNo.1430
"会社"さん
(0) 2002/03/24 19:16 [メール表示する]

上池と申します。
>
> 質問を見た瞬間に条件反射的に「Visitorパターンじゃないのか、これ?」
> と思って *見ていました* (汗)
方法Aの方は私もVisitorパターンでの実装が可能だと思います。さらに方
法Bの方はBridgeパターンだと思います。
Visitorパターンは、Visitor役にAcceptor役の具体的なクラスの具体的な
名前を記述する必要がある欠点とのトレードオフで、それぞれの具体的な
Acceptor役に合わせてVisitor役の振る舞いを切り替えられるおいしさが
生まれます。
しかしながら方法Aの場合だとVisitorパターンで実装するなら、Visitor役
に記述する名前は抽象的なものとなります。私見ですが、それなら無理
にVisitorパターンを実装する必要はないように思えます。
MyDataから派生する具体的なクラスが同列なら、実装と機能を分けて拡
張していけるBridgeパターンを利用した方法Bの方がこの場合いいのでは
ないでしょうか?もし同列でなく、MyDataの実装クラスによって、MyWriter
の実装クラスもまた振る舞いを変える必要性があるのならば、Visitorパタ
ーンを用いて方法Aでもいいかもしれません。

-
MLNo.1431
さん
(0) 2002/03/24 23:18 [メール表示する]


陽の字です。
拙い質問に親切なアドバイス、ありがとうございます。
ご指摘の通り、例としてあげた方法は厳密には
「Templateパターンには則っていない」
「むしろ他のパターンで記述した方がよい」
ということになると思います。
ただ、今回主眼を置きたいのは
「既存のパターンのサンプルプログラムを作成してみよう」
ということではなく、
「具体的なライブラリを設計中で、ある箇所をどう実装すべきか悩んだので、
デザインパターンの学んでいる方の目にはどちらが定石として映るのか、を
知りたかった」ということだったのです。
パターンに厳密にマッチしているかどうかをケアしなかったのはそのせいです。
↑
(もっとも、やはり用語は正しい定義に従って用いるべきですね、ごめんなさい。:
−))
試作中のライブラリにVisitorやBridgeのどちらが向いているか(もしくは他の手が
あるのか)を検討してみることにします。
考えがまとまったら、また投稿させていただきます。

-
MLNo.1435
Masashi Umezawaさん
(0) 2002/03/25 13:39 [メール表示する]

こんにちは
梅澤です。
もう終わった話なのかもしれないですが、
On Sun, 24 Mar 2002 23:22:56 +0900
"Yohichiro Hattori"wrote:
> 「具体的なライブラリを設計中で、ある箇所をどう実装すべきか悩んだので、
> デザインパターンの学んでいる方の目にはどちらが定石として映るのか、を
> 知りたかった」ということだったのです。
どのようなデザインパターンを選ぶと良いかは、そのデザインを適用するに
あたってコンテキストや制約の情報がないといけませんよね。いろいろな
回答がでてきてしまうのもそのためと思います。
> 試作中のライブラリにVisitorやBridgeのどちらが向いているか(もしくは他の手が
> あるのか)を検討してみることにします。
> 考えがまとまったら、また投稿させていただきます。
直感的には、私ならAをベースに以下のようにします。(Javaをしばらく書いて
いないので変なところがあったらすみません)
abstract public class MyData {
public void write() {
this.writeWith( this.getWriter() ) ;
}
protected void writeWith(MyWriter writer){
writer.process(this); //strategyに書かせる
}
public MyWriter getWriter() {
if(writer == null){ //lazy initialization
writer = this.defaultWriter();
}
return writer;
}
protected MyWriter defaultWriter(){
new MyDefaultWriter(); //factory method
}
public void setWriter(MyWriter writer) {
this.writer = writer;
}
}
こうするとMyApp側では単にwrite()というだけで済むようになるので
クライアント側のコードがすっきりします。
public class MyApp {
…
void writeAll() {
for (int i = 0; i < dataArray.length; i++) {
dataArray[i].write();
}
}
…
}
書き出しのフォーマットを切り替えたい場合には外からwriterをセット
します。
public class MyApp {
…
void writeAllCSV() {
CSVWriter csvWriter = new CSVWriter();
for (int i = 0; i < dataArray.length; i++) {
dataArray[i].setWriter(csvWriter);
}
this.writeAll();
}
…
}
ただWriter側では、MyDataのサブクラスに応じていろいろと処理を切り替え
ないといけないですね。これはVisitorでなくStrategyを使っているからです。
もっともリフレクションが使える言語であったり、Strategyに渡すデータを
メタデータも含めた形に汎用化できるのであれば、その必要もないですね。
abstract public class MyData {
...
protected void writeWith(MyWriter writer){
writer.process(this.asDescription());
}
protected String? asDescription(){
//自身の持つデータをたとえばXML文字列にして返す
return description;
}
...
}
以上、ご参考まで。
---
[:masashi | ^umezawa]


