概要
改めて基本を学ぶ。
Rubyによるデザインパターン第1章。
デザインパターンとは
- プログラミングにおいて繰り返し現れる問題に対する、適切解のパターン。
- 無駄無く設計されたオブジェクト指向プログラムの実現をサポート。
パターンとしてカタログ化されていることで
車輪の再発明を防ぐ
デザインパターンの根底にある5つの考え
- 変わるものを変わらないものから分離する
- プログラムはインターフェイスに対して行う(実装に対して行わない)
- 継承より集約
- 委譲、委譲、委譲
- 必要になるまで作るな(YAGNI)
変わるものを変わらないものから分離する
ソフトウェアの仕様には必ず変更が加わるもの。
変わるものと変わらないものを分離しておくことで、
「仕様の変更」に対して「システムの変更」を出来る限り局所的にする。
プログラムはインターフェイスに対して行う(実装に対して行わない)
可能な限り「一般的・抽象的なもの」に対してプログラミングすること。
(ここで言うインターフェイスは、Javaの組み込み構文としてのインターフェイスではなく、
より広いレベルで「抽象度を高めたもの」を意味する。)
これにより、全体のコードの結合度を下げる。
具体性が高く、密結合なコード
if is_car?
my_car = Car.new
my_car.drive(200)
else
my_plane = AirPlane.new
my_plane.fly(200)
end
乗り物が増える度にコード全体に変更が必要(変更に弱い)。
抽象度が高く、疎結合なコード
my_vehicle = get_vehicle
my_vehicle.travel(200)
乗り物の数が増えても本コードに変更を加える必要はない(変更に強い)。
継承より集約
継承は望ましくないつながりを作ってしまう。
具体的には
- サブクラスの振る舞いは、スーパークラスの振る舞いに依存する。
- サブクラスからスーパークラスの中身を覗くことが出来る。
class Vehicle
def start_engine
# エンジンを動かすためのもろもろの処理..
end
def stop_engine
# エンジンを止めるためのもろもろの処理..
end
end
class Car < Vehicle
def drive
start_engine
# driving..
stop_engine
end
end
- Carからエンジンの実装が丸見え
- エンジンを使用しない乗り物を作りたい場合は大改造が必要
→変わりやすい部分(Engine)を変わりにくい部分(Vehicle)から分離できていない。
代替案
集約を使う。
つまり、
オブジェクトに、「他のオブジェクトに対する参照」を持たせる。
オブジェクトが何かの一種である(is-a-kind-of)関係は避けて、
何かを持っている(has-a)関係にする。
Car has a Engineの例
class Car
def initialize
@engine = Engine.new
end
def drive
@engine.start
# driving..
@engine.stop
end
end
class Engine
def start
# エンジンを動かすためのもろもろの処理..
end
def stop
# エンジンを止めるためのもろもろの処理..
end
end
これでEngineがVehicleから分離され、またカプセル化された。
これにより、Engineの手軽な切り替えが可能に。
class Car
def initialize
@engine = GasolineEngine.new
end
def drive
@engine.start
# ガソリンエンジンでドライブ..
switch_to_diesel
# ディーゼルエンジンでドライブ..
@engine.stop
end
def switch_to_diesel
@engine = DieselEngine.new
end
end
委譲、委譲、委譲
委譲(delegation)
(継承パターンと同じように)start_engineとstop_engineを
クラスの外に公開したいこともある。
そんな場合でも、処理の実態はEngineクラスに任せてしまう。
class Car
def initialize
@engine = GasolineEngine.new
end
def drive
@engine.start
# driving..
@engine.stop
end
def switch_to_diesel
@engine = DieselEngine.new
end
def start_engine
@eigine.start # Engineクラスに任せる
end
def stop_engine
@eigine.stop # Engineクラスに任せる
end
end
これで「継承より集約」を実現しつつ、継承時と同じ機能を持つクラスが作成できた。
つまり、
集約と委譲の組み合わせは、強力かつ柔軟な継承の代替手段。
※ start_engineやstop_engineは委譲のために書かなければならない無駄なコードに思えるが、
method_missing等の利用で解決できる。
これはProxyパターンで別途詳細する。
必要になるまで作るな(YAGNI/You Ain't Gonna Need It.)
「将来使うかも」は、大抵使わない。
いたずらに柔軟性を持たせようとオーバーエンジニアリングして
コードの複雑性を増していては、本末転倒。
デザインパターン自身は目的ではない。
問題を解決し、目的を達成するための手段として役立てるべきであり、それ以上のものではない。
デザインパターンを考える上での戒め。
以上5原則を前提に
各パターンを見ていく。
【Template Method】-テンプレは準備した、あとはお好きに-
http://qiita.com/kidachi_/items/7c2a80bfc8a87a05051f
【Strategy】-取り替え可能パーツ群を戦略的に利用せよ-
http://qiita.com/kidachi_/items/02f8f6df955ba1d0e93b
【Observer】-本日のニュースをお届けします-
http://qiita.com/kidachi_/items/ce18d2a926c558159689
【Composite】 -世界は再帰的(部分は全体、全体は部分)-
http://qiita.com/kidachi_/items/6cb73b2bbc875e9bef6d
【Iterator】-君の子供たちに伝えたいのだけど-
http://qiita.com/kidachi_/items/afa4c6c29a6eb6be487a
その他WIP.