Edited at

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

More than 1 year has passed since last update.


参考

@kidach1 さんの投稿をPythonに書き換えてるだけです。

@kidach1 さん、いつもありがとうございます。

https://qiita.com/kidach1/items/4b63de9ad5a97726c50c


概要

改めて基本を学ぶ。

参考「Rubyによるデザインパターン第1章」→この投稿はPython


デザインパターンとは


  • プログラミングにおいて繰り返し現れる問題に対する、適切解のパターン。

  • 無駄無く設計されたオブジェクト指向プログラムの実現をサポート。

パターンとしてカタログ化されていることで

車輪の再発明を防ぐ


デザインパターンの根底にある5つの考え


  • 変わるものを変わらないものから分離する

  • プログラムはインターフェイスに対して行う(実装に対して行わない)

  • 継承より集約

  • 委譲、委譲、委譲

  • 必要になるまで作るな(YAGNI)


変わるものを変わらないものから分離する

ソフトウェアの仕様には必ず変更が加わるもの。

変わるものと変わらないものを分離しておくことで、

「仕様の変更」に対して「システムの変更」を出来る限り局所的にする。


プログラムはインターフェイスに対して行う(実装に対して行わない)

可能な限り「一般的・抽象的なもの」に対してプログラミングすること。

(ここで言うインターフェイスは、Javaの組み込み構文としてのインターフェイスではなく、

より広いレベルで「抽象度を高めたもの」を意味する。)

これにより、全体のコードの結合度を下げる。

具体性が高く、密結合なコード

if ( is_car()  ):

my_car = Car()
my_car.drive(200)
else:
my_plane = AirPlane()
my_plane.fly(200)

乗り物が増える度にコード全体に変更が必要(変更に弱い)。

抽象度が高く、疎結合なコード


my_vehicle = get_vehicle()
my_vehicle.travel(200)

乗り物の数が増えても本コードに変更を加える必要はない(変更に強い)。


継承より集約

継承は望ましくないつながりを作ってしまう。

具体的には


  • サブクラスの振る舞いは、スーパークラスの振る舞いに依存する。

  • サブクラスからスーパークラスの中身を覗くことが出来る。

class Vehicle:

def start_engine(self):
# エンジンを動かすためのもろもろの処理..
print("engine started")
def stop_engine(self):
# エンジンを止めるためのもろもろの処理..
print("engine stopped")

class Car(Vehicle):
def drive(self):
self.start_engine()
# driving..
self.stop_engine()

car = Car()
car.drive()

Carからエンジンの実装が丸見え

エンジンを使用しない乗り物を作りたい場合は大改造が必要

→変わりやすい部分(Engine)を変わりにくい部分(Vehicle)から分離できていない。

代替案

集約を使う。

つまり、

オブジェクトに、「他のオブジェクトに対する参照」を持たせる。

オブジェクトが何かの一種である(is-a-kind-of)関係は避けて、

何かを持っている(has-a)関係にする。

Car has a Engineの例

class Car:

def __init__(self):
self.engine = Engine()

def drive(self):
self.engine.start()
# driving..
self.engine.stop()

class Engine:
def start(self):
print ("engine started")

def stop(self):
print ("engine stopped")

car = Car()
car.drive()

これでEngineがVehicleから分離され、またカプセル化された。

これにより、Engineの手軽な切り替えが可能に。

class Car:

def __init__(self):
self.engine = GasolineEngine()

def drive(self):
self.engine.start()
# ガソリンエンジンでドライブ..
self.switch_to_diesel()
# ディーゼルエンジンでドライブ..
self.engine.stop()

def switch_to_diesel(self):
self.engine = DieselEngine()


委譲、委譲、委譲

委譲(delegation)

(継承パターンと同じように)start_engineとstop_engineを

クラスの外に公開したいこともある。

そんな場合でも、処理の実態はEngineクラスに任せてしまう。

class Car:

def __init__(self):
self.engine = GasolineEngine()

def drive(self):
self.engine.start()
# driving..
self.engine.stop()

def switch_to_diesel(self):
self.engine = DieselEngine()

def start_engine(self):
self.engine.start() # Engineクラスに任せる

def stop_engine(self):
self.eigine.stop() # Engineクラスに任せる

これで「継承より集約」を実現しつつ、継承時と同じ機能を持つクラスが作成できた。

つまり、

集約と委譲の組み合わせは、強力かつ柔軟な継承の代替手段。

※ start_engineやstop_engineは委譲のために書かなければならない無駄なコードに思えるが、

method_missing等の利用で解決できる。

これはProxyパターンで別途詳細する。


必要になるまで作るな(YAGNI/You Ain't Gonna Need It.)

「将来使うかも」は、大抵使わない。

いたずらに柔軟性を持たせようとオーバーエンジニアリングして

コードの複雑性を増していては、本末転倒。

デザインパターン自身は目的ではない。

問題を解決し、目的を達成するための手段として役立てるべきであり、それ以上のものではない。

デザインパターンを考える上での戒め。


以上5原則を前提に

各パターンを見ていく。

【Template Method】-テンプレは準備した、あとはお好きに-

https://qiita.com/kotetsu75/items/2900eac0fa24f09f775b

【Strategy】-取り替え可能パーツ群を戦略的に利用せよ-

https://qiita.com/kotetsu75/items/186af7006b44703c0379

【Observer】-本日のニュースをお届けします-

https://qiita.com/kotetsu75/items/719a8fc2e4cc7789c1b4

【Composite】 -世界は再帰的(部分は全体、全体は部分)-

制作中

【Iterator】-君の子供たちに伝えたいのだけど-

制作中