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

無料レッスン

オブザーバとステート

このレッスンのあとで: 変化を多くのオブジェクトに知らせるオブザーバと、状態で動きが変わるステートを書くことができる。

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

オブザーバパターンとは何か

オブザーバとは、あるオブジェクトの変化を、登録した複数のオブジェクトに自動で知らせる型です。ステートとは、状態ごとにクラスを分けて、状態によって動きを変える型です。

どちらも、if の連続や、相手の名前の直書きをなくし、変更に強い作りにします。

このレッスンのゴール

メモ

変化を多くのオブジェクトに知らせるオブザーバと、状態で動きが変わるステートを書くことができる。

キーワード

ことばいみれい
オブザーバ変化の知らせを受け取る側のオブジェクト。観察者とも呼ぶ
サブジェクト変化を知らせる側。観察される対象とも呼ぶShelf
登録と解除知らせを受け取り始めることと、やめることsubscribe
通知サブジェクトが、登録された全員の update を呼ぶこと
ステート状態ごとにクラスを分けて、動きを切り替える型
状態の遷移次の状態へ移ることWaiting から Running へ

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

オブザーバは変化を登録した全員に知らせる

棚の在庫が減ったとき、補充の係にも、記録の係にも知らせたいとします。棚が、係を名前で直接呼ぶと、係が増えるたびに棚を直すことになります。

棚は、知らせる相手のリストだけを持ちます。変化があれば、全員の update を呼びます。これがオブザーバです。

在庫が減ると、見張り役に知らせる

コード: python
class Shelf:
    def __init__(self):
        self.count = 10
        # 知らせる相手を、リストに入れておきます。
        self.watchers = []

    def subscribe(self, watcher):
        self.watchers.append(watcher)

    def take(self, amount):
        self.count = self.count - amount
        # 相手が誰かは知らず、update と呼ぶだけです。
        for watcher in self.watchers:
            watcher.update(self.count)


class LowAlarm:
    def update(self, count):
        if count < 3:
            print("補充が必要です。残りは", count)


shelf = Shelf()
shelf.subscribe(LowAlarm())
shelf.take(4)
shelf.take(5)
# take(5) を take(2) に変えて、もう一度実行しましょう。

例: 棚の在庫と通知

操作残りの数見張り役の動き
最初の状態10知らせは来ない
take(4)63より多いので、何も出さない
take(5)13より少ないので、補充の知らせを出す

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

ステートは状態ごとにクラスを分ける

ジョブには、待機中、実行中、完了の3つの状態があります。ボタンを押したときの動きは、状態で違います。

状態の文字列と if で分けると、状態が増えるたびに if が増えます。状態をクラスにして、press をそれぞれに書くのがステートです。

状態をクラスにして、次へ進める

コード: python
class Waiting:
    def press(self):
        return Running()

    def label(self):
        return "待機中"


class Running:
    def press(self):
        return Done()

    def label(self):
        return "実行中"


class Done:
    def press(self):
        return self

    def label(self):
        return "完了"


# 状態を、状態のオブジェクトとして持ちます。
state = Waiting()
for step in range(3):
    print(state.label())
    state = state.press()

例: ジョブの状態の移り変わり

今の状態press の答え次の状態
WaitingRunning を返す実行中
RunningDone を返す完了
Done自分自身を返す完了のまま

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

ラボで2つのパターンを動かす

ラボの端末で、棚のファイルとジョブのファイルを作り、動かします。6つの Run を、上から順に押してください。

出力は英語にしてあります。端末の文字コードに、頼らないためです。

作業用のフォルダを作る

コマンド
mkdir -p ~/oop/behaviour

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

そのフォルダへ移る

コマンド
cd ~/oop/behaviour

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

棚のファイルを書く

コマンド
printf 'class Shelf:\n    def __init__(self):\n        self.count = 10\n        self.watchers = []\n\n    def subscribe(self, watcher):\n        self.watchers.append(watcher)\n\n    def take(self, amount):\n        self.count = self.count - amount\n        for watcher in self.watchers:\n            watcher.update(self.count)\n\n\nclass LowAlarm:\n    def update(self, count):\n        if count < 3:\n            print("low stock", count)\n\n\nclass Recorder:\n    def __init__(self):\n        self.history = []\n\n    def update(self, count):\n        self.history.append(count)\n\n\nshelf = Shelf()\nrecorder = Recorder()\nshelf.subscribe(LowAlarm())\nshelf.subscribe(recorder)\nshelf.take(4)\nshelf.take(5)\nprint(recorder.history)\n' > shelf.py

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

棚を動かす

コマンド
python3 shelf.py

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

知らせは在庫が3より少ないときだけ出る

出力は、low stock 1 と [6, 1] の2行です。

take(4) で残りが6のとき、補充の知らせは出ません。take(5) で1になると出ます。記録係は、どちらの回も受け取り、[6, 1] を覚えています。

ジョブのファイルを書く

コマンド
printf 'class Waiting:\n    def label(self):\n        return "waiting"\n\n    def press(self):\n        return Running()\n\n\nclass Running:\n    def label(self):\n        return "running"\n\n    def press(self):\n        return Done()\n\n\nclass Done:\n    def label(self):\n        return "done"\n\n    def press(self):\n        return self\n\n\nclass Job:\n    def __init__(self):\n        self.state = Waiting()\n\n    def press(self):\n        self.state = self.state.press()\n\n\njob = Job()\nfor step in range(4):\n    print(job.state.label())\n    job.press()\n' > job.py

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

ジョブを動かす

コマンド
python3 job.py

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

完了のあとは、何度押しても完了のまま

出力は、waiting、running、done、done の4行です。

Done の press は自分自身を返すので、4回目も完了のままです。状態の移り変わりは、クラスの中に入っています。

よくある間違い

注意

「知らせる側が、相手を名前で直接呼べばよい」と考える人が多くいます。それでは、相手が増えるたびに、知らせる側を書き換えます。リストに登録して、同じ update を呼べば、書き換えずに済みます。

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

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