decorator

仕事で最近 C++ を使っている。以前、C でシステムを組み始めたのだが、結局クラスを使わずに関数と構造体だけではまともにモジュール化出来そうに無いので C++ にしてしまった。

例えば Java だと基底クラスのメソッドを参照するのに super とやれば行けるのだが、C++ で似たような事をしようにも、文法上全然出来ない。なぜだろうと考えてみれば、C++ は多重継承が使えるせいかと納得する。
後はコンストラクタを定義していて思うのだが Java のように this を使って異なるシグネチャのコンストラクタを呼び出せないので、いささか不便な気もする。これも、基底クラスの初期化をメソッドのシグネチャの中に含ませている事と多重継承のせいなのだろうか。
またコンストラクタの中で初期化用のメソッドを呼び出しているが、それを派生クラスで書き換えてもなぜか、コンストラクタ実行時は基底クラスの物を呼び出してしまう。デフォルトの挙動について基底クラスで定義しているので純粋仮想関数にはできないが、とりあえず virtual と前につけて仮想関数にしても駄目である。どうやらコンストラクタが絡むと、通常のルールでは処理されないのだろうか。この辺は多分に処理系依存のような気もする。少し不便だ。

コンストラクタ以外にも、特定のクラスのメソッド関数を素直にスレッドで走らせられなかったり*1、若干不便な気もするのだが、それでも CPPUNITlog4cxx などのライブラリも導入して、何とか Java ちっくに開発を進めている。日付の扱いに関しては Java のほうが断然便利だと思うのだが、これも慣れなのだろうか。ただ文字列の書式付出力については、printf 関数や iostream クラスなどが使えるので、C++ の方が素直に便利だと思う。

色々なデータを低レベル I/O に出力するクラスを書いている。いわいるファイルディスクリプタを使って出力先を指定して、様々な形式のデータを出力するものである。関心事としてデータフォーマットと出力メディア*2の二種類の物が出てくるが、これらは互いに独立しており、組み合わせて使いたい。多分に C++ ちっくに素直に考えれば多重継承となるのだろうが、定義すべきクラスの数が組み合わせ的に増加しそうだし、なんかスマートでないのでパス。

Java で最近の流行に乗っかるのなら、多分にアスペクト指向の考え方を導入して AspectJ となるのだろう。関心事が上手い具合に分離している。主要な関心事は特定のデータフォーマットを適切に扱う事で、入出力のメディアは抽象化して考えたい。*3 という事で出力メディアに関する処理は Aspect として定義して、後で結合する。そんな感じであろうか。まー多分、そんな感じであろう。C++ にも AspectC++と言うのが有るらしいが、システムのポータビリティも下がるし、考えてみれば、アスペクト指向に於けるちゃんとしたモジュールの設計技法って、自分の身になっていない。仕事で作成しているので納期の事も考えるとあまり現実的でない。という事でパス。

結局、あーでもない、こーでもないと考えていたが、久々に GoFデザインパターン本(オブジェクト指向における再利用のためのデザインパターン)を読んでいると、decorater パターンと言うのが載っていて、今回の目的に合致しているので利用する事にした。要は、派生クラスの中で、基底クラスへのポインタをメンバ変数としてもち、基本は単なる移譲で、必要に応じて、フックをかけるような形でメソッド関数を再定義すると言う物である。あまり Java では見かけないパターンである。考えてみれば、C++ では派生クラスで再定義したメソッド関数からは基底クラスのメソッドは完全に隠れてしまうので、ある意味重宝な所もある。ただ Java ならば super.someMethods と基底クラスのメソッドにアクセスできるから、あまりありがたみが無いのかもしれない。

あと、基底クラスの内容を効率良く拡張する上で、ホットスポットとコールドスポット*4を明確にして、ホットスポットの部分はフックメソッドで定義して、テンプレートパターンを適用するというのも良く行う手だと思う。ただこの場合、仕様が流動的だと、暑いか冷たいかの判断が難しい上、冷たい所の内容が多くなるとフックメソッドの数や、引数の数が増えて管理に困る事も多々ある。それにフックメソッドはデータフォーマットと出力メディアの2種類の関心事に応じて少なくとも2つ以上は存在するわけで、結局多重継承と同じ問題にぶち当たる気がする。この辺 decorator ならプログラムの中で動的に組み合わせて使うので、クラスの数はかなり抑えられる。それにフレームワークの中で利用しているクラスなので、複合的な機能を持たせても、呼び出し側に何の影響がないのは、結構便利な物である。

ただ、同じ型を持つクラスが一方はデータフォーマットの違いに応じて分類され、もう一方では、出力メディアの違いで分類されてと、若干違和感を感じる部分もある。それに、どのメソッドをどのように修飾(decorete)すべきかなのは、基底クラスの処理全体を理解しないと難しい所もあると思う。それを容易するには基底クラスのメソッドがデータフォーマットと出力メディアの2つの関心事について、きれいに分離させる事が望ましいが、その辺の設計はけっこう難しいかもしれない。

とりあえずそんな事を考えながら、C++ で出力用をクラスを最近作っていた。decorator としての基底クラスのメソッドに virtual 修飾子をつけなかったので、派生クラスで該当するメソッドを書き換えても、基底クラスのポインタで呼び出しを使う decorater パターンでは派生クラスで書き換えたメソッドが呼び出されなかったり*5、それならば、少なくとも protected で定義したメソッドはすべて virtual にしようと自己ルールを考えたり、GoFデザインパターンの本はサンプルが C++ なので、こういう時はありがたいものだと、なぜか感心したり。パターンの肝にあたる部分の実装はさすがに集中力を要したが、一度基本が組みあがると、データフォーマットと出力メディアの種類を増やす作業はあっけないほど楽に進んだ。些細な事かもしれないが、気付きを素直に喜べる仕事の仕方が出来てすこし嬉しく思う。

流行を追うのに疲れてしまったのだろうか。

日進月歩なプログラミング技術の世界でも、少し古い技術について、じっくり考えてみるのは、結構意義のあることだし、懐古趣味ではないと思う。雨が降りすぎれば、大きな木は育たないと思う。要は問題を適切に捉えて、既存の技術に上手に適用させるような手法も大切なような気がする。多分にデザインパターンは、設計結果を事例(とその動機付け)付きのパターンカタログとして、分かりやすく提示した所にその価値があったのだろうが、多様なパターンをオブジェクトの性質の側面から分類して提示したために、あたかも汎用性が高く、万能であるかのような印象を与えてしまったのかもしれない。これを問題の性質に応じて分類し直せば*6、もう少し使いがっての良い物になったような気もする。

とりあえず、久々にきれいなフレームワークが出来た。これが Java ではなく、個人的に制約の多い C++ であるというのも少し考えさせられる。茶道みたいな物か。

オブジェクト指向における再利用のためのデザインパターン

オブジェクト指向における再利用のためのデザインパターン

*1:クラス内部で friend 関数を定義して、そこから呼び出すようにして解決している。別に friend でなくても良いが。

*2:例えば、ファイルか、シリアル出力か、TCP/IPイーサネットか等の種別

*3:その方が個々の検証も楽になると思う

*4:仕様上、変更の多そうな所と処理内容がほとんど固定しているところ。

*5:Java なら全然意識しなくても良いのだが。・・ C++ は多態の扱いがなんか中途半端だ。

*6:とは言うものの、オブジェクトの側面も対象となる問題に間接的には関連しているので、全くの間違いではないと思う。