クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

ファクトリとストラテジーとデコレータ

このレッスンのあとで: 困りごとに合わせて、ファクトリ、ストラテジー、デコレータを選んで書くことができる。

65分のレッスンコマンド6件毎日使う Linux コマンドのラボ:エンジニアが暗記している30のコマンド

デザインパターンとは何か

デザインパターンとは、よく出会う設計の問題に対する、名前の付いた定番の解き方です。このレッスンでは、ストラテジー、ファクトリ、デコレータの3つを使います。

パターンは、先に選ぶものではありません。まず困りごとがあり、それに合う定番を選びます。

このレッスンのゴール

メモ

困りごとに合わせて、ファクトリ、ストラテジー、デコレータを選んで書くことができる。

キーワード

ことばいみれい
デザインパターンよくある設計の問題に対する、名前の付いた定番の解き方
ストラテジー計算のしかたを部品にして、取り替えられるようにする型
ファクトリ名前などを受け取り、どのクラスを作るかを決める部品make_shipping("free")
デコレータ中の部品と同じ呼び方を持ち、機能を重ねて包む型
インターフェース部品が持つべきメソッドの名前と引数の決まり

学生が入力するセルは、このプレビューでは空白です。

ストラテジーは計算のしかたを取り替える

送料は、いつも500円とは限りません。3000円以上なら無料にする店も、いつも500円の店もあります。

送料の計算を、クラスにして部品にします。注文は、部品に fee と頼むだけです。計算のしかたの部品をストラテジーと呼びます。

送料の計算を部品にする

コード: python
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円FlatShipping500円2500円
2000円FreeOver(3000)500円2500円
3500円FreeOver(3000)0円3500円

学生が入力するセルは、このプレビューでは空白です。

ファクトリは、どのクラスを作るかを決める

画面の選択肢や設定ファイルから来るのは、"free" のような名前の文字列です。名前から、どのクラスを作るかを決める所が要ります。

その仕事を1か所にまとめた関数がファクトリです。使う側は、クラスの名前を知らずに、オブジェクトを手に入れます。

名前から送料の部品を作る

コード: python
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 の @ で書く関数の飾りとは別のものです。

呼ばれた回数を数える入れ物

コード: 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つで足りる

学生が入力するセルは、このプレビューでは空白です。

よくある間違い

注意

「パターンは、たくさん使うほど良い設計だ」と考える人が多くいます。それは間違いです。困りごとがないのにパターンを入れると、読む人が迷うだけです。まず困りごとを書き出しましょう。

このラボとソースについて: 毎日使う Linux コマンドのラボ:エンジニアが暗記している30のコマンド

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。