Edited at

デザインパターンの話

More than 5 years have passed since last update.

irxground 君が再考: GoF デザインパターンといふ記事を書いてゐるので自分もちょっとコメントしてみます。

基本的に irxground 君と同意見のところは省略します。

あと、GoF の本自体は私は読んでゐません。

(GoF のパターン以外のパターンに関する意見の方が長くなってますね……。)


GoF のデザインパターン


生成に関するパターン


Builder

そもそも builder パターンは Java の String と StringBuilder の様に可変オブジェクトと不変オブジェクトを別のクラスに設計しなければならない言語でしか基本的に役に立たないパターンであり、C++ の様にキャストだけで可変オブジェクトを不変オブジェクトに変換できる言語ではこのパターンは無用なはずである。

Java が出る前の本でこれがパターンとして挙げられてゐたといふのが俺には不思議に感じられる。

あと builder パターンに触れながら引数オブジェクトに触れないのは片手落ちだらう。(あるメソッドへの引数をまとめただけの新しいクラスを導入したのなら、そのメソッド自体もその新しいクラスの中に移動できるはずである。さうすると、これはもはや引数オブジェクトパターンではなく関数オブジェクトの builder である。)


Factory method

コンストラクタは本質的にメソッドの一種に過ぎないので、コンストラクタだからどうとかメソッドだからどうとか云ふのはあまり意味が無い。

コンストラクタでないメソッドにできてコンストラクタにできないことは、戻り値の型を自由に設定することだ。故に、このパターンは外から自由にそのクラスのインスタンスを作らせたくない (または作れない) ときに使ふ。すなはち、シングルトンや具象クラスの動的選択などである。


振る舞ひに関するパターン


Mediator

まづこれは Façade とは関係が無い。

Mediator は複数のオブジェクト間の参照を疎結合にし、オブジェクトの所有・被所有関係 (あるいは主従関係と言っても良い) を確立しやすくするための設計指針と考へるべし。

よってこのパターンは dependency inversion principle と共に学ぶ必要がある。


State

状態によって振る舞ひを変へたい場合に、各状態オブジェクトが関数オブジェクトとして扱へるやうになってゐれば便利な場合もあるかもしれない。(特に、それぞれの状態オブジェクトが固有のデータを持ってゐたり複雑な処理を抱へこんでゐる場合)

単純なケースでは enum と switch 文を使った方が分かりやすからう。


GoF 以外の設計に関するパターン


Null object

基本的に使へるなら使ふべきパターン。特に、「if 文で null かどうか判定して null でなければメソッドを呼び出す」の様なパターンがいくつもある場合は設計が怪しいので null object パターンを真剣に検討した方が良い。


並行処理に関するパターン


Active object

アクターを実装するための設計パターン。自分で実装する場合はスレッドプールについても理解しておく必要があらう。

Active object パターンよりもアクターの概念自体を理解することの方が重要。


Balking

単なるガード。


Immutable object

同じオブジェクトを複数スレッド間で共有する場合に便利なやり方であり、今後は必須のやり方とみなされるべきもの。基本的に、スレッド間で直接参照・共有するデータはすべて不変でなければならないと考へるべきである。(なぜなら、可変データの参照は排他制御を要求するからである)


Proactor

GUI アプリでのバックグラウンド処理等で使ふべきパターン。自分で実装するには scheduler についても理解する必要があらう。


Scheduler

タスクをいつどのように実行するかの抽象化。スレッドプールを自分で管理したりする場合には必要なもの。


Thread pool

スレッドの生成・破棄が重い処理系における高速化のための小細工。


Worker thread

Thread pool に近いが、特定のスレッドが特定の種類の処理を行ふために待ち構へてゐる点に重きがある。Worker thread にタスクを渡すために scheduler を使ふのが定石。


Future/promise

Future には結果を受け取るための仕組みとして join とコールバックの二種類 (またはその両方) がある。

Future のすばらしいところは、それを使はずに直接 join したりコールバックを渡したりするのに比べて、join やコールバック設定を行へるところの柔軟性がとても高い点にある。特にコールバックが設定できる future は worker thread や active object と親和性が高い。


Thread per message

これは基本的にはアンチパターンである。

処理を要求されるたびに無秩序に新しいスレッドを作っていたのでは、同じ処理が複数スレッドで同時に走る状況が容易に起こり得る。それは果たして意図された挙動なのか? 前の処理が終はらない内は次の処理を始めないように制御すべきなのではないか? 仮に同時処理を認めるとしても、複数スレッドが同じデータにアクセスする際に適切に同期処理が行はれてゐるのか? 後から始まった処理が前に始まった処理よりも先に終はった時、それぞれの結果は正しく反映されるか? ――考へるべきことは非常に多い。

GUI アプリで重たい処理をバックグラウンドスレッドで行ふのにこのパターンを用ゐがちであるが、これではなく proactor (と scheduler, worker thread などの組合はせ) や active object を使ふのが正解である。

クライアントにデータを送り返すウェブサーバーなど、各スレッドの処理が独立してゐる場合はこのパターンが適用可能かもしれないが、普通は worker thread の使用を検討する。


ブロッキングを伴ふパターン

ブロッキングは、GUI アプリでは基本的にやってはいけない。なぜなら、UI スレッドをブロックさせることはアプリをフリーズさせることを意味するからである。


Join

分割統治法の並列処理で便利。


排他制御関係

排他制御は Mutex クラスとか synchronized キーワードとか処理系によって色色な名前と形で提供されるが、基本的に低レベルな同期機構である。

排他制御は、ロックを獲得する際にブロッキングを発生させる可能性がある。「どれだけ長い間待たされうるか」をきちんと予測できてゐない限り、安易に排他制御によってスレッド間の同期をしてはいけない。

特に、GUI アプリ等の高レベルなビジネスロジックを実装するプログラムで直接排他制御をいぢるのは避け、Future やアクターモデル等の高レベルな同期機構のみを使った方がよい。

また、ロックによる排他制御ではデッドロックが起こらない様に複数のロックの関係についても気を使ふ必要がある。


Double-checked locking

このパターンは、コードの書き方やコンパイラの最適化の仕方などによっては極めて低い確率でバグる場合があることが知られてゐる。言語や API が処理系に対して許してゐる最適化について熟知してゐるのでなければ、このパターンを使ってはいけない。


Guarded suspension, monitor

排他制御では必須の概念。


Read-write lock

単なるロックの変種。