本投稿は私の主観によって書かれています。コメントは大歓迎です。もし長くなるようでしたら別途記事に投稿し、リンクを張っていただけると嬉しいです。
概要
GoFのデザインパターンは適当すぎるから、いい加減、修正されるべき。
参考までに各パターンに対するコメントを書く。
GoFのデザインパターン
GoFのデザインパターンは適当であり、教科書通りに学ぶべきものではないように思う。
以下がGoFのデザインパターンの良くない原因だろう。
- 本が出版されたのは1994年であり、Java(1995)が出てくるよりも前だった
- オブジェクト指向が未成熟な時代にカタログ化された
- 現代のプログラミングと合致しないものが多い
- 「オブジェクト指向における~」と断っている以上、OOPに絡める必要があった
- パターンのいくつかに「多態性を用いると便利」という蛇足がついている
- 挙げたパターンに根拠がない
- 「とりあえず、23個ほど列挙してみました」でしかない
- 説明に目的と実装が入りまじっており、まともな定義になっていない
- 人(書籍)によって言っていることが違う
デザインパターンに対するコメント
生成に関するパターン
Abstract Factory
Factoryクラスを切り替えることで生成するインスタンスを切り替えることができる。
OOPの良いデザインパターンだと言える。
しかし極めて普通のOOPの使い方であり、生成に限定したからこのような名前を付けただけ、とも取れなくない。
Builder
複雑なオブジェクトを生成するのに、生成専用のクラスを用意するのは有用である。
しかしこのパターンで多態性による切り替えに言及すべきではない。
Factory Method
コンストラクタの代わりに生成用関数を用意するのは有用である。
C++のコンストラクタでは複雑な処理や例外は避けるべきであるし、Javaでもコンストラクタから子クラスのインスタンスを返すことはできない。
このパターンも多態性による切り替えに言及すべきでない。
Factoryメソッドは生成したいクラスに用意すると良いと思う。
Prototype
複雑なインスタンスを生成したいのであればBuilderを作るべきである。
単にcloneしたいのであればそれはデザインパターンではなく、メソッドを用意しただけである。
このパターンは不要だろう。
Singleton
グローバルの免罪符として使われている現状をどうにかしないといけない。
「状態を持たず多態性を利用したい」という状況でのみ使うべきである。
多態性を利用しないのであれば、static
を利用したほうが単純性が増す。
構造に関するパターン
Adapter
インタフェースをそろえるためにラッパーを用意することはよくある。
普通のOOPの使い方である。
Bridge
interface
の説明をしているだけに感じる。
Composite
GUIなどによくあるパターンである。
これはデザインパターンにふさわしい内容だろう。
一応「委譲による継承」も参考に。
Decorator
「委譲による継承」としてまとめるべきである。
Facade
よく使われそうなメソッドだけ、わかりやすいところに出しておく。
これは良い設計であり、デザインパターンにふさわしいだろう。
Flyweight
JavaのInteger
クラスなどが実例。
OOPでなくても使うが、省メモリのためのパターンとして良いだろう。
Proxy
直接触る代わりに、中間層を置くのは悪くない手法である。
汎用的過ぎて、デザインパターンと言えるのかは微妙。
例えば、直接Stringを触る代わりにHtmlString
とRawString
にWrapして使おう。というのも中間層を置いていることに変わりはない。
また、マルチスレッドで使われるFuture
パターンも同様である。
中間層はあらゆる問題の解決に利用される。
振る舞いに関するパターン
Chain of Responsibility
「委譲による継承」としてまとめるべきである。
Command
このパターンで複数のことを説明しすぎである。
まず、関数そのものをオブジェクトとし「切り替えて使える」ことはStrategy
の説明に押し付けるべきであった。
命令を抽象化しクラスにするのは良い設計である。
例えばcanExecute
とexecute
を持つようなクラスを用意することは凝集度が高まってよい。
migrate
とrollback
などでも同様である。
Interpreter
「構文木もオブジェクトで表現できる」以上のことは何も言っていない。
無意味である。
Iterator
多態性が有用であることのサンプルの一つ。
iterator自体は知っておいて損はないだろう。
現在においては言語組み込みとなっていることが多く、改めてデザインパターンで学ぶ必要はないかもしれない。
Mediator
内向きのFacade
パターンである。ではなかった。コメントを受けて修正。
クラス間の疎結合を満たすために仲介クラスを用意するのは良い設計である。
Godクラスにならないように注意する必要がある。
Memento
状態を元に戻したいという要求はある。
しかしこれの実装はまさに実装依存であるので、デザインパターンとして列挙するにはおかしい。
Observer
OOPのデザインパターンとしてふさわしい内容だろう。
ただ「関数オブジェクト」で置き換え可能な場合が多く、学ばなくてもよいかもしれない。
State
OOPの良くないパターンである。
状態遷移図があるような遷移を各クラスで表現すると、凝集度が下がりコードが読みづらい。
普通にstate
フィールドとif
で書いたほうがわかりやすい。
一方、表を書くほどではなく、単に次へ次へと進んでいくような場合は悪くない設計である。
ゲームプログラムにおいてScene
クラスとして知られている。
Strategy
単なる関数オブジェクトである。
Template Method
非常によく使うが、単なるabstract
メソッドの使い方である。
デザインパターンかといわれると判断しづらい。
Visitor
良くはないが、やむを得ないデザインパターンである。
State同様、凝集度が下がりコードの見通しが悪くなる。
ScalaやRubyなど、クラスでパターンマッチができる言語であればそちらを利用すべき。
そもそもこのパターンを使う機会はほとんどないのではないか。
木構造で有名なものはファイルシステムだが、実際にはisDir
のような関数を用いる。
自分も構文木をたどるときしか使った覚えがない。
(構文木を扱うプログラマは数少ないだろう)
委譲による継承
RubyにRackというインタフェース(あるいは約束)があり、これはリクエストを受けてレスポンスを返す抽象的なWebサーバーである。
class Greet
def call(env)
req = Rack::Request.new(env)
return [200,
{ 'Content-Type' => 'text/plain' },
["Hello, #{req.param['name']}"]
end
end
さらにRackMiddleware
があり、これはRackインスタンスをラップし自身がRackインスタンスとして振る舞う。
class CacheMiddleware
def initialize(app)
@app = app
end
def call(env)
@cache ||= {}
@cache[env['REQUEST_PATH']] ||= @app.call(env)
end
end
さてこのパターンを何と呼ぶのだろう?
Decorator
か、Chain of Responsibility
か。
今回はキャッシュなのでChain of Responsibility
だというかもしれないが、JSONPに対応させればDecorator
だというだろう。
もしリクエストパスで分岐するのであればComposite
パターンになる。
これは単純に「委譲による継承」パターンとすべきだろう。
最後に
これは私の主観(経験則)なので、ほかの人は別のことを考えるだろう。
ここで挙げていないが有用なデザインパターンも数多くある(Loanパターンなど)
誰か有識者がまとめてくれないだろうか。
コメント募集(再)