生成系デザインパターンをライブラリ化するには

デザインパターンによる定型的な構造を何度も何度もプログラムしていると、デザインパターンそのものをライブラリ化して再利用してしまいたいと考えるようになる。

そんなさなかにジェネリクスのテクニック*1を覚えると、いままで不可能だったStrategyパターンとかAbstract Factoryパターンとかのライブラリ化ができるようになって、スゲーッ!これで怖いものなしだぜ!とか思いあがるわけだけど、他のデザインパターン、具体的にFlyweightパターンなんかをライブラリ化しようとして言語仕様上のどうしようもないところにぶち当たって挫折してしまうものなんだ。

ここでFlyweightパターンについて簡単に説明すると、同じ値を持つインスタンスは単一であることが保証されるというデザインパターンだ。Javaでは列挙型がこの挙動を示す。列は常に同じ値のものは同じ参照に集約される。Stringも近い挙動を示すが、Stringは同値の別のインスタンスを生成できてしまうからFlyweightとは言えない。なんとなくイメージできるだろうか。

コンストラクタを規定できない問題

そもそもJavaはコンストラクタの形状を継承させることはできない。例えばAというクラスにint型の引数をひとつもつコンストラクタをつくるとする。そしてB extends AとしてクラスBを作ったとしよう。このとき、クラスBではクラスAのコンストラクタを明示的に呼び出さなくてはならない。

public class A {
    public A(int i) {}
}

public class B extends A {
    public B() {
        super(0);
    }
}

今、サンプルソースではsuper(0)として、クラスAのA(int)という形のコンストラクタを引数に定数0を渡して呼び出した。このクラスBは必ずクラスAに定義されたいずれかのコンストラクタを呼ばなくてはならないが、しかし、クラスAと同じ形状のコンストラクタを持つ必要はない。

現に、このクラスBのコンストラクタは引数を持たない形であって、クラスAのコンストラクタとは違うものだ。

端的にいってしまえば、オブジェクトは生成された後であればポリモフィズムにより、親の型と代入互換性をもち、同じように操作することが出来るが、ことオブジェクトの生成に関しては同じような操作で作ることは出来ない。コンストラクタはポリモフィズムできない。

これが故にライブラリ内部でインスタンスを生成する…ということはできない。例えリフレクションを用いたとしても、そのライブラリの使用にはとても型システム的に不確かな制約が課せられることとなるだろう。*2

Flyweightを共有するためのパラダイム

では、Flyweightパターンをライブラリで共有するために必要なプログラミングパラダイムってのはなんなんだろう。どういうパラダイムを持った言語ならば、Flyweightパターンをライブラリ化することができるんだろうか。

Flyweightパターンをライブラリとして実装するなら

  1. new演算子であるクラスのオブジェクトがインスタンス化されようとしたとき
  2. そのコンストラクタが動く手前で、引数が既知の組み合わせであるかを確認する
  3. 既知のものであれば、キャシュされていたインスタンスを返す(処理終了)
  4. 未知のものであればコンストラクタを起動する
  5. コンストラクタにより生成されたインスタンスをキャッシュしておく
  6. 生成されたインスタンスを返す(処理終了)

といった流れになるだろうか。

ここで、ライブラリはコンストラクタの引数の型については興味がない。ユニーク*3、つまり無二のものかどうかだけが重要だ。

そして、コンストラクタの前後に処理を挟みたい。差し挾むといえば、そう、アスペクト指向だ。Flyweightパターンをライブラリ化したければ、コンストラクタにアスペクトをウィービング*4できなくてはならない。

このアスペクトは、インスタンス単位にアスペクトをウィービングし分ける必要はない。あくまでクラスの宣言の段でFlyweightアスペクトをウィービングできればいい。このような、いわば去勢されたアスペクト指向*5を言語に持たせることで生成にまつわるデザインパターンを、一度きりの記述で使いまわすことが出来るようになる。

デザインパターンが死ぬとき

ScalaではSingleton パターンは死んだ。何故なら、言語仕様に基づき、objectキーワードを使用するだけでそのクラスはSingletonとなる。デザインパターンに基づく実装など存在しない。

同じように、幾つかのデザインパターンはパターンではなくなって死ぬべきだと僕は考えている。そういう意味でFlyweightパターンというのは分かりやすく象徴的なものだと思う。

僕はいつかこの手でFlyweightを殺したいと思っている。

追記

b:id:SiroKuro それDIで何とかできない?

DIはアスペクト指向を実現する実装の一種とも捉えられるので、つまるところアスペクト指向で実現するという話に帰着しちゃうわけですね。

もっとも、僕の思想としてはDIもまた死ぬべき過渡期の技術だということになるわけです。DIも殺したいなぁ。

*1:class Hoge>のような型を使うことで実装クラス自身の型を扱うテクニックがあって、これがデザインパターンをライブラリ化するのに役立つ。参考:ジェネリクスを最大限活用するための設計技法

*2:引数が特定の型のコンストラクタを作っておけ、といったJavaの型システムではチェックできないことをドキュメントで強要するような泥臭い設計などなど

*3:重複のない、無二の存在をユニーク(unique)と表現するが、一般的な日本語ではユニークというと「風変わりな」「面白い」という訳の意味でばかり使われるので、業界擦れしてないプログラマは首を傾げる語

*4:weaving : 縫い付けるの意。アスペクトを適用させること

*5:というのも、あらゆるところでアスペクトがウィービングできると処理の見通しが悪くなる。これはアスペクト指向のデメリットとして知られている