デザインパターンとは何か
デザインパターンとは、よく出会う設計の問題に対する、名前の付いた定番の解き方です。このレッスンでは、ストラテジー、ファクトリ、デコレータの3つを使います。
パターンは、先に選ぶものではありません。まず困りごとがあり、それに合う定番を選びます。
このレッスンのゴール
メモ
困りごとに合わせて、ファクトリ、ストラテジー、デコレータを選んで書くことができる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| デザインパターン | よくある設計の問題に対する、名前の付いた定番の解き方 | |
| ストラテジー | 計算のしかたを部品にして、取り替えられるようにする型 | |
| ファクトリ | 名前などを受け取り、どのクラスを作るかを決める部品 | make_shipping("free") |
| デコレータ | 中の部品と同じ呼び方を持ち、機能を重ねて包む型 | |
| インターフェース | 部品が持つべきメソッドの名前と引数の決まり |
学生が入力するセルは、このプレビューでは空白です。
ストラテジーは計算のしかたを取り替える
送料は、いつも500円とは限りません。3000円以上なら無料にする店も、いつも500円の店もあります。
送料の計算を、クラスにして部品にします。注文は、部品に fee と頼むだけです。計算のしかたの部品をストラテジーと呼びます。
送料の計算を部品にする
class FlatShipping:
def fee(self, total):
return 500
class FreeOver:
def __init__(self, limit):
self.limit = limit
def fee(self, total):
if total >= self.limit:
return 0
return 500
class Purchase:
def __init__(self, total, shipping):
self.total = total
# 送料の計算のしかたを、部品として持ちます。
self.shipping = shipping
def to_pay(self):
return self.total + self.shipping.fee(self.total)
print(Purchase(2000, FlatShipping()).to_pay())
print(Purchase(2000, FreeOver(3000)).to_pay())
print(Purchase(3500, FreeOver(3000)).to_pay())
例: 送料のストラテジーと支払額
| 商品の合計 | 部品 | 送料 | 支払う額 |
|---|---|---|---|
| 2000円 | FlatShipping | 500円 | 2500円 |
| 2000円 | FreeOver(3000) | 500円 | 2500円 |
| 3500円 | FreeOver(3000) | 0円 | 3500円 |
学生が入力するセルは、このプレビューでは空白です。
ファクトリは、どのクラスを作るかを決める
画面の選択肢や設定ファイルから来るのは、"free" のような名前の文字列です。名前から、どのクラスを作るかを決める所が要ります。
その仕事を1か所にまとめた関数がファクトリです。使う側は、クラスの名前を知らずに、オブジェクトを手に入れます。
名前から送料の部品を作る
class FlatShipping:
def fee(self, total):
return 500
class FreeOver:
def __init__(self, limit):
self.limit = limit
def fee(self, total):
if total >= self.limit:
return 0
return 500
# 名前を受け取り、使うクラスを選んで、オブジェクトを返します。
def make_shipping(name):
if name == "flat":
return FlatShipping()
if name == "free":
return FreeOver(3000)
return None
print(make_shipping("free").fee(3500))
print(make_shipping("nothing"))
デコレータは同じ形のまま機能を重ねる
送料の部品が、何回呼ばれたかを数えたいとします。部品の中を書き換えたくありません。
中の部品を包み、同じ fee を持つ入れ物を作ります。入れ物は、数えてから中に渡します。これがデコレータです。Python の @ で書く関数の飾りとは別のものです。
呼ばれた回数を数える入れ物
class FlatShipping:
def fee(self, total):
return 500
# 中の部品と同じ fee を持ち、呼ばれた回数も数えます。
class Counted:
def __init__(self, inner):
self.inner = inner
self.calls = 0
def fee(self, total):
self.calls = self.calls + 1
return self.inner.fee(total)
shipping = Counted(FlatShipping())
shipping.fee(1000)
shipping.fee(2000)
print(shipping.calls)
ラボで3つのパターンを重ねる
ラボの端末で、3つのパターンを1つのファイルにまとめて動かします。4つの Run を、上から順に押してください。
最初に free の部品を使い、次に flat の部品に取り替えて、出力の違いを見ます。
作業用のフォルダを作る
mkdir -p ~/oop/patternsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
そのフォルダへ移る
cd ~/oop/patternsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
3つのパターンのファイルを書く
printf 'class FlatShipping:\n def fee(self, total):\n return 500\n\n\nclass FreeOver:\n def __init__(self, limit):\n self.limit = limit\n\n def fee(self, total):\n if total >= self.limit:\n return 0\n return 500\n\n\nclass Counted:\n def __init__(self, inner):\n self.inner = inner\n self.calls = 0\n\n def fee(self, total):\n self.calls = self.calls + 1\n return self.inner.fee(total)\n\n\ndef make_shipping(name):\n if name == "flat":\n return FlatShipping()\n return FreeOver(3000)\n\n\nshipping = Counted(make_shipping("free"))\nprint(shipping.fee(2000), shipping.fee(3500))\nprint(shipping.calls)\n' > shipping.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
動かす
python3 shipping.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
2000円は500円、3500円は無料になる
出力は、500 0 と 2 の2行です。2000円には500円、3500円には0円の送料がかかっています。
2行目の 2 は、入れ物が fee を2回呼ばれたと数えた結果です。
ファクトリへの名前を flat に替える
sed -i 's/make_shipping("free")/make_shipping("flat")/' shipping.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
もう一度動かす
python3 shipping.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
困りごとから、使うパターンを選ぶ
出力は 500 500 と 2 です。名前を1つ替えただけで、3500円にも送料がかかるようになりました。
下の表のように、困りごとを先に決めて、合う定番を選びます。クラスが1つで足りるなら、パターンは要りません。
例: 困りごとと、合うパターン
| 困りごと | 選ぶパターン | 合図 |
|---|---|---|
| 計算のしかたを、場面で取り替えたい | ストラテジー | if と elif が種類ごとに続く |
| 名前から、作るクラスを決めたい | ファクトリ | 作る場所が何か所にも散る |
| 今の部品に、機能を重ねたい | デコレータ | 中を書き換えたくない |
| 変える所が1つもない | パターンは要らない | クラスが1つで足りる |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
「パターンは、たくさん使うほど良い設計だ」と考える人が多くいます。それは間違いです。困りごとがないのにパターンを入れると、読む人が迷うだけです。まず困りごとを書き出しましょう。