Top / Pattern | English |
1. | What's New | |
2. | 概要 | |
3. | サンプル | |
4. | 仕様 概要 | |
5. | bgen(1) | Part-B ジェネレータ |
6. | jjAggregate | 集約 |
7. | jjCollect, jjDCollect | コレクション、双方向コレクション |
8. | jjCollect1, jjDCollect1 | 列、双方向列 |
9. | jjHash | ハッシュテーブル |
10. | jjGraph | グラフ |
11. | 私の考え | |
12. | Reference | |
13. | History |
jjPattern と C++操縦法に書かれているオリジナルものとの違いを挙げます:
また、ハッシュの使用例も示しています。
実行方法:
$ make sample01j |
P1 Object Oriented S/W Construction by Meyer Theory of Budo by Tugumasa Nangou : : |
ソースの解説:
クラスとして、App, Publisher, Author, Book があり、それぞれ以下のような関係があります:
関係 | 意味 |
App : Publisher = 1:n | プログラム内で複数の出版者を扱う |
App : Author = 1:n | プログラム内で複数の著者を扱う |
Publisher : Book = 1:n | 出版者は複数の本を扱う |
Author : Book = 1:n | 一人の著者は複数の本を書く |
Hash in Book | Book にハッシュキーをつける |
jjPattern の特徴は、クラス間の関係(パターン)をクラスとは独立に記述することで、クラス相互の依存関係を切り離し、クラス間の独立性を高めることです。具体的に、クラス間の関係はソースコードの以下の部分で記述しています:
jjCollect (publishers, App, Publisher); jjCollect (authors, App, Author); jjCollect (books, Publisher, Book); jjAggregate (byAuthor, Author, Book); jjHash (isbn_hash, App, Book); |
各クラス(App,Publisher,Author,Book)ではお互いの関係を記述していない点に注目して下さい。これは、bgen(1) というプリプロセッサが、上の関係定義式から必要な情報を読みだし、各クラスに埋め込むということで自動化されています。各クラスの EXT_クラス名 というマクロが埋め込み情報に置き換わります。
jjPattern のもう1つの特徴が、検索の高速さです。
関係がクラス内に自動的に埋め込まれるため、検索の連鎖が高速に行えます。main() の最後の while 文を見て下さい。これは、iterator を使って全出版社を検索し、個々の出版社で扱っている本を表示し、その著者を表示しています。
出版社の検索は全件のシーケンシャルサーチです。従って、これには高速性の特徴はありませんが、その次の本検索と著者検索が、全てポインタによる直接検索である点がこの jjPattern の特徴です。
同様なことを DBMS や Hash ライブラリなどを使用すると、こういった類の検索(出版社→本、本→著者)でもキー検索となりますが、jjPattern は直接ポインタを使用しているため、非常に高速になるのです。
class Node { EXT_Node : }; jjCollect(tree, Node, Node); |
$ make sample02j |
この場合、複数の Part-A ファイルから1つの Part-B を生成する 必要があります。 sample03*.cpp, sample03*.h はその一例です。
実行方法:
$ make sample03 |
sample04.cppがそのサンプルです。
実行方法:
$ make sample04 |
ここで、n は要素数で、O(n)は「要素数に比例した処理時間かかる」を意味し、 O(log n)は「要素数の log(n)に比例した処理時間がかかる」ことを意味します。
Pattern | Pattern Methods | Iterator Methods | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
add | ins | rep | fwd | bwd | del | child (first) | parent (from*1) | last | sel | num | ++ | -- | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
jjCollect1 | -,4*2
1
| 1
| n
| 1
| -
| n
| 1
| -
| 1
| -
| 1
|
| 1
| -
| jjCollect
| 4,4
| 1
| 1
| n
| 1
| -
| n
| 1
| -
| 1
| -
| 1
|
| 1
| -
| jjDCollect
| 4,8
| 1
| 1
| 1
| 1
| 1
| 1
| 1
| -
| 1
| -
| 1
|
| 1
| 1
| jjDCollect1
| -,8
| 1
| 1
| 1
| 1
| 1
| 1
| 1
| -
| 1
| -
| 1
|
| 1
| 1
| jjAggregate
| 4,8
| 1
| 1
| n
| 1
| -
| n
| 1
| 1
| 1
| -
| 1
|
| 1
| -
| jjHash
| *,4*3
| 1
| -
| -
| -
| -
| 1
| -
| -
| -
| 1
| 1
|
| 1
| -
| jjGraph
| 4,12
| 1
| -
| -
| 1
| 1
| n
| 1
| 1
| 1
| -
| 1
|
| 1
| -
| |
$ make size...とします。
add(C *, E *); | コンテナCの最後に要素Eを追加する。 -Hash以外 コンテナCに要素Eを追加する。-Hash |
add(C *, E *e1, E *e2); -Hash以外 | コンテナCの指定した要素e1の次にe2を追加挿入する。 |
ins(C *, E *); -Hash以外 | コンテナCの最初に要素Eを挿入する。 |
ins(C *, E *e1, E *e2); -jjDCollect, jjDCollect1のみ | コンテナCの指定した要素e1の前にe2を追加挿入する。 Cが空の場合、また e1 が NULL の場合、単に e2 を追加する。 |
rep(C *, E *e1, E *e2); | コンテナCの指定した要素e1をe2と置き換える。 |
fwd(E *); | 要素E の次の要素を返す。 E が最後の要素の場合、最初の要素を返す。 |
bwd(E *); | 要素E の前の要素を返す。 E が最初の要素の場合、最後の要素を返す。 |
del(E *); -jjAggregate del(C *, E *); -Other | 要素E を削除する。 jjAggregate, jjCollect では O(n) のコストがかかる。 |
first(C *); first(); -jjCollect1, jjDCollect1 child(C *); | コンテナC の最初の要素を返す(jjCollect, jjDCollect, jjAggregate)。
列の最初の要素を返す(jjCollect1, jjDCollect1)。 child() と first() は同じであるが、jjCollect1, jjDCollect1 では 意味的に child() は適当でないので、別名で first() を 用意した。 (Ver1.8) また、last() との対比で first() を全順序列コンテナに 設けた。 |
parent(E *); | 要素E を包含しているコンテナC を返す。 jjAggregateのみで有効。 |
last(C *); --jjAggregate, jjCollect, jjDCollect, jjGraph last(); --jjCollect1, jjDCollect1 | コンテナC の最後の要素を返す(jjAggregate, jjCollect
, jjDCollect, jjGraph)。 列の最後の要素を返す(jjCollect1, jjDCollect1)。 |
sel(C *, E *key); | コンテナCからkey にマッチする要素を検索して返す。 jjHash のみで有効。 |
num(C *); | コンテナ C の要素数を返す |
start(C *); | Iteratorの再初期化。 全要素をスキャンする場合。 |
start(C *, E *start); | Iteratorの再初期化。 特定の要素 E から始める場合。 |
start2(E *start); --for jjAssoc | Iteratorの再初期化。 特定の要素 E から始める場合。 (2002/08/30 変更)start() -> start2() に変更。 jjAggregate(pattern, Node, Node) などのように C, E 共に 同じ型を指定した Tree パターンを形成する際、start(C) と start2(E) として区別するため。 |
start(E *start, E *end); | Iteratorの再初期化。 特定の範囲(start,end)をスキャンする場合。 |
operator++ | 次の要素を返す。 最後の要素を返した後、更に operator++() を呼び出すと、NULL が返される。 |
operator-- | 前の要素を返す。 最初の要素を返した後、更に operator--() を呼び出すと、NULL が返される。 |
bgen(1) - B Part GeneratorSYNOPSIS
bgen [-l libFile] sourcefile ...DESCRIPTION
jjPattern を使用している sourcefile を読み込んで、Part-B (クラスの埋め込み情報)を標準出力に生成するプリプロセッサ。生成された Part-B は sourcefile の中で読み込まれます(include)。 実行ファイルを創るまでのフローは以下のようになります:FILESsourcefile | V bgen -> sourcefile.b | sourcefile <-+(include) | V C++ Compiler | V Executable file-l libFile
jjPattern ライブラリファイルを標準以外のものを指定する場合
inst_root/share/lib.bg - jjPattern 定義ファイルSEE ALSO
(not yet)BUGS
(not yet)
しかしながら、2つ以上のクラス間で相互に関連しあっているようなアプリケーションの場合、jjCollect1 よりは jjCollect の方が本ライブラリの特徴が活きてきます。そのため、上の順番で習得されることをお薦めします。
jjAggregate -集約
SYNOPSIS
#include <jj/pattern.h> jjAggregate(P, Top, Bot); void P_class::add (Top *, Bot *); void P_class::ins (Top *, Bot *); Bot* P_class::first (Top *); Bot* P_class::child (Top *); Bot* P_class::last (Top *); void P_class::del (Bot *); Bot* P_class::fwd (Bot *); Top* P_class::parent (Bot *); int P_class::num (Top *); P_iterator::P_iterator(); P_iterator::P_iterator(Top *); void P_iterator::start(Top *); void P_iterator::start2(Bot *); Bot* P_iterator::operator++();
DESCRIPTION
1:多の関係を実現するパターンの1つです。親へのポインタを持つ点が類似の 他のパターンに無い特徴です。
┌─┐ │ │Top └─┘ ↑ ↑ ↑ ↓ │ │ ┌─┐ ┌─┐ ┌─┐ │ │─→│ │─→│ │・・・ Bot └─┘ └─┘ └─┘双方向Aggregate である jjDAggregate はまだ実装していません。
jjCollect, jjDCollect -コレクション、双方向コレクション
SYNOPSIS
#include <jj/pattern.h> jjCollect(P, Top, Bot); jjDCollect(P, Top, Bot); void P_class::add (Top *, Bot *); void P_class::add (Top *, Bot *b, Bot *b2); --add b2 after b void P_class::ins (Top *, Bot *); --insert to top void P_class::ins (Top *, Bot *b, Bot *b2); --insert b2 before b jjDCollect only Bot* P_class::first (Top *); Bot* P_class::child (Top *); Bot* P_class::last (Top *); void P_class::del (Top *, Bot *); Bot* P_class::fwd (Bot *); Bot* P_class::bwd (Bot *); --jjDCollect only int P_class::num (Top *); P_iterator::P_iterator(); P_iterator::P_iterator(Top *); void P_iterator::start(Top *); void P_iterator::start(Top *, Bot *); --jjCollect only void P_iterator::start2(Top *, Bot *);--jjDCollect only void P_iterator::start3(Bot *, Bot *);--jjDCollect only Bot *P_iterator::operator++(); Bot *P_iterator::operator--(); --jjDCollect only
DESCRIPTION
1:多の関係を実現するパターンの1つです。
┌─┐ │ │Top └─┘ ↓ ┌─┐ ┌─┐ ┌─┐ │ │←→│ │←→│ │・・・ Bot └─┘ └─┘ └─┘
親へ戻るポインタは持っていません。双方向Collection である jjDCollect は必要に迫られたので作成しました。
jjCollect1, jjDCollect1 -列、双方向列
SYNOPSIS
#include <jj/pattern.h> jjCollect1(P, Bot); jjDCollect1(P, Bot); void P_class::add (Bot *); void P_class::ins (Bot *); Bot* P_class::first (); Bot* P_class::last (); void P_class::del (Bot *); Bot* P_class::fwd (Bot *); Bot* P_class::bwd (Bot *); int P_class::num (); P_iterator::P_iterator(); void P_iterator::start(); void Q_iterator::start(Bot *); --jjDCollect1 only Bot* P_iterator::operator++(); Bot* P_iterator::operator--(); --jjDCollect1 only
DESCRIPTION
列を実現するパターンの1つです。
first() ↓ ┌─┐ ┌─┐ ┌─┐ │ │←→│ │←→│ │・・・ Bot └─┘ └─┘ └─┘
jjHash -ハッシュ
SYNOPSIS
#include <jj/pattern.h> jjHash(P, Holder, Element); void P_class::add(Holder *, Element *); void P_class::del(Holder *, Element *); Element*P_class::sel(Holder *, Element *key); int P_class::num(Holder *); P_iterator::P_iterator(); P_iterator::P_iterator(Holder *); void P_iterator::start(Holder *); Bot* P_iterator::operator++(); //implement int P_class::hash(Element *); int P_class::cmp(Element *in_hash, Element *key); // convenient hash functions int jjstr_hash(char *); // utility function void P::put_stat(Holder *);
DESCRIPTION
キーインデックスを実現するパターン。 SYNOPSIS にも記述してあるように、2つのメソッド P_class::hash(), P_class::cmp() はハッシュパターンを使用する 側で提供しなければなりません。それぞれの仕様は以下です:
Holder Elements ┌─┐ ┌─┐ ┌─┐ ┌─┐ │ │→│ │─→│ │─→│ │・・・ Bot ├─┤ └─┘ └─┘ └─┘ │ │→ ・・・ ├─┤ ・ ・ ├─┤ │ │ └─┘P_class:hash(key) - keyの正数ハッシュ値を返す
P_class::cmp(data,key) - data == key なら 0を, data != key なら !0 を返す
ここで、key, data は Element 型へのポインタ。
(2002/09/08 追加) なお、cmp() において、 data は hash 内の element、key は検索キーという違いがある点を 仕様として明記します。
ユーティリティとして文字列用ハッシュ関数 jjstr_hash() を提供しています。 文字列関係のハッシュパターンを構築したい場合、これを P_class::hash() の実装に使用することができます。
P::put_stat() は、ユーティリティの1つで、 ハッシュの衝突状況を表示するものです。
jjGraph -グラフ
SYNOPSIS
#include <jj/pattern.h> jjGraph(P, N, E); void P_class::add (N *from, E *edge, N *to); E* P_class::first (N *); E* P_class::last (N *); void P_class::del (E *); E* P_class::fwd (E *); N* P_class::from (E *); N* P_class::to (E *); int P_class::num (N *); P_iterator::P_iterator(); P_iterator::P_iterator(N *); void P_iterator::start(N *); E* P_iterator::operator++();
DESCRIPTION
「グラフ」とは、点と線からなるものを指します。 下にグラフの一例を示します:
jjGraph は有向グラフを使用するためのパターンです。 グラフの数学的な定義は:
G = (N, E)です。これに対応するグラフパターンの定義は以下のようになります:ここで、N = 点集合(node set)、E = 辺集合(edge set)
class N { EXT_N ...};sample04.cppも参考のこと。
class E { EXT_E ...};
jjGraph(G,N,E);点 N の集合それ自体に対する処理、例えば
for all n in N ...に相当するメソッドは jjGraph 自身では提供していませんが、 必要ならば jjCollect パターンなどを使って 別途宣言することで対応します。
本ライブラリ jjPattern の原点である「C++操縦法」 の言うところの「パターン」は、今注目されている「 デザインパターン」の中の MEDIATOR パターンに相当する、C++ ライブラリ に位置付けられます。それはそれとして、「C++操縦法」の内容は非常にユニーク です。
Soukup氏は「プログラムを複雑にしているのは、 モジュール間の相互依存のせいである。 そのために個々のモジュールを独立してテストすることが難しくなっている。」 と指摘し、「モジュールをパターンとデータ構造に分け、 フラットな2階層型のモジュール関係を築くことで、モジュール間の独立性を増し、 単体テストを行いやすくし、ひいてはアプリケーションの複雑さを低減させる」 という方法を提案します。
その中から出てきたのが、著書の中にもでてくる、コンテナクラスを主とする パターンライブラリです。一見、 常識としてのオブジェクト指向に反しているような設計ですが、 氏の文章は非常に説得的で、私は 自分の PC で確認しながら理解してきました。
この書の中で非常に面白かったのは、同じ課題を Smalltalk, C++ Patternライブラリ, C++ NIHライブラリで作成し、比較するところです。 Patternライブラリを使用すると Smalltalk とほぼ同じステップ数で書けるあたり、 興味をひきます。
もう1つ、オブジェクトの永続化に関する議論をしているところも 非常に面白い内容でした。Disk access を主とする OODBMS(Object Oriented Database Management System)と、主記憶上での access を主とする永続データを比較している点です。メインメモリが 1G を超えることも普通になってきた昨今、1G程度の永続データなら全て on memory で高速に処理できることを考えれば、氏のアプローチも有望なものと思われます (今回、私の提供する jjPattern には永続データの機能は残念ながらありません)。
今までこのライブラリを極私的なプログラミングで使ってきた経験から言うのも 一面的かも知れませんが、公正を期すために言えば、パターン・ライブラリは非 常に癖があります。
1. まず、構造体にパターンを埋め込む(ZZ_EXT_...)という方法のため、当然ながら int, double などの基本型にパターンライブラリは直接使用できません。ここが STL と比較して弱いところかも知れません。 基本型にパターンライブラリを使用するためには、その基本型を含む class か struct を面倒でも定義する必要があります。
2. 次に、本の中に書かれているままのロジックでは、簡単に暴走するプログラム が書けてしまう、という問題があります。例えば、「C++操縦法」pp.128〜pp.131 のサンプルプログラムの中で、一旦 S1L1 に add したオレンジ o2 を削除(del) せずに S2L1 に add すると、pointer のループができ、iterator での検索時に 無限ループに陥ってしまいます。もちろん、このレベルの問題はこの書の範囲外 なのかもしれませんが...。
以下に、私なりの理解で簡単に要約します。
1. STL は「関係」をオブジェクトの中に埋め込む、ということをしないため、関連する検索の度に hash などのキー検索を使わざるを得ません。これは RDBMS がそうなのと同じです。一方、「C++操縦法」のアプローチは、SAMPLEにも示したように、ポインタによりダイレクトに「関係」を表現しているため、処理も速く簡単になる、という点が大きく異なります。
2. 先にも書きましたが、STL はどのデータにも適用できますが、jjPattern ではクラス型しか適用できません。
3. それと、jjPattern にはありませんが、 Soukup氏の会社 のちゃんとした市販ライブラリにはオブジェクトの永続化が可能となっていますが、 STL には無いようです。
「オブジェクト指向でないからだめ」などと言う狭い見方はする必要はないのではないか、 と個人的には思っています。
Erich Gamma / Richard Helm / Ralph Johnson / John Vlissides=著、本位田真一 / 吉田和樹=監訳 「デザインパターン(原題:Design Patterns)」、ソフトバンク、ISBN4-89052-797-4
'99-07-03 | Ver.1.0 | Initial Version |
'99-09-20 | Ver.1.0.1 | Delete jjpattern.h::_jjCollect_iterator::_jjCollect_iterator |
'00-01-15 | Ver.1.02.0 | Add Tree feature, jjCollect1, jjDCollect1. Write English version of this document. |
'01-04-20 | Ver.1.3 | support multiple Part-A files |
'01-05-02 | Ver.1.4 | add jjGraph pattern |
'03-03-24 | Ver.1.5 | jjHash size is now dynamic! |
'03-09-18 | Ver.1.6 | num() and ins() are introduced |
'06-02-10 | Ver.1.8 | first() and last() are applied to all sequential container patterns |