Ruby
programming
デザインパターン

Rubyによるデザインパターン5原則

More than 3 years have passed since last update.


概要

改めて基本を学ぶ。

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.