LoginSignup
964
1093

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-01-10

参考

@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】-君の子供たちに伝えたいのだけど-
制作中

964
1093
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
964
1093