← 一覧に戻る
振る舞いに関するパターン

Observer

状態の変化を通知する

概要

別名Pub-Sub(Publish-Subscribe)パターン

  • Publish: 発行
  • Subscribe: 購読

マンガでわかる Observer

マンガでわかる Observer #デザインパターン - Qiita

でざぱたんで覚える Observer

ちびキャラは「オブザーバーたん」。疎開先で仲間外れにされ、ひとりで山や川を眺めて過ごす少女。「声をあげなきゃ、そこに居たことすら覚えていてくれない」——どれだけ見ていても、先に自分を登録(=声をあげる)した者だけが通知の輪に入れる、というイベントリスナー登録の関係を孤独として描く。登録の仕組みを隠蔽しないと依存が強まるのでMediator併用が良い、という補足まで載っている。

出典: いしだけ『でざぱたん: ちびキャラで覚えるデザインパターン』(P.119〜)

登場人物

  • Subject: 観察される側のインタフェース
  • Observerに自身の変化をnotifyする
  • ConcreteSubject
  • Observer: 監視役。Subjectから「状態が変わりましたよ」と通知を受けるインタフェース
  • 一つのSubjectを監視するObserverは複数存在して良い
  • ConcreteObserver

クラス図

Observer-pattern-class-diagram.png (600×320)

このサイトの実装(乱数生成の通知)での対応関係:

classDiagram
  class Observer {
    <<interface>>
    +update(generator)
  }
  class NumberGenerator {
    <<abstract>>
    -observers ArrayList
    +addObserver(observer)
    +deleteObserver(observer)
    +notifyObservers()
    +getNumber() int
    +execute()
  }
  class RandomNumberGenerator {
    -random Random
    -number int
    +getNumber() int
    +execute()
  }
  class DigitObserver {
    +update(generator)
  }
  class GraphObserver {
    +update(generator)
  }
  Observer <|.. DigitObserver
  Observer <|.. GraphObserver
  NumberGenerator <|-- RandomNumberGenerator
  NumberGenerator o-- Observer : observers
  Observer ..> NumberGenerator : update(generator)

やり方

  • SubjectにObserver達を持たせる
  • Observerにupdate(Subjectの状態変化があったことを受け取り、何かする)を実装
  • Subjectの状態変化があったら、Observer全員に処理を促す
  • その際Subject自身のインスタンス情報をObserverに渡すと、状態ごとの何かができていいよね

メリット(用途)

交換可能性

  • ConcreteSubjectはObserverには依存しているがConcreteObserverには依存していない
  • ConcreteObserverはSubjectには依存しているがConcreteSubjectには依存していない

関連

  • Meditator
  • 共通点: 複数の対象に状態変化を通知
  • 相違点(目的の違い)
  • Mediator: 処理を中央化する
  • Observer: 個々に状態変化を通知して同期を取る
Java
Observer.java
public interface Observer {
    public abstract void update(NumberGenerator generator);
}
DigitObserver.java
public class DigitObserver implements Observer {
    public void update(NumberGenerator generator) {
        System.out.println("DigitObserver:" + generator.getNumber());
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
    }
}
GraphObserver.java
public class GraphObserver implements Observer {
    public void update(NumberGenerator generator) {
        System.out.print("GraphObserver:");
        int count = generator.getNumber();
        for (int i = 0; i < count; i++) {
            System.out.print("*");
        }
        System.out.println("");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
    }
}
NumberGenerator.java
import java.util.ArrayList;
import java.util.Iterator;

public abstract class NumberGenerator {
    private ArrayList observers = new ArrayList();        // Observerたちを保持
    public void addObserver(Observer observer) {    // Observerを追加
        observers.add(observer);
    }
    public void deleteObserver(Observer observer) { // Observerを削除
        observers.remove(observer);
    }
    public void notifyObservers() {               // Observerへ通知
        Iterator it = observers.iterator();
        while (it.hasNext()) {
            Observer o = (Observer)it.next();
            o.update(this);
        }
    }
    public abstract int getNumber();                // 数を取得する
    public abstract void execute();                 // 数を生成する
}
Main.java
public class Main {
    public static void main(String[] args) {
        NumberGenerator generator = new RandomNumberGenerator();
        Observer observer1 = new DigitObserver();
        Observer observer2 = new GraphObserver();
        generator.addObserver(observer1);
        generator.addObserver(observer2);
        generator.execute();
    }
}
Go

Java版のabstract class NumberGenerator(add/delete/notifyObserversは共通実装、getNumber/executeは抽象)を、NumberGenerator interfaceとbaseNumberGenerator構造体(embedding用)に分けて表現する。Goのembeddingは「今embeddingしている外側の具象型」を仮想呼び出しできないため、notifyObservers(self NumberGenerator)のようにselfを明示的に渡すことで、Java版notifyObservers()内のthis(動的束縛)を再現している(FactoryMethod版GoのCreate(c Creator, ...)と同じ手筋)。

実行: go run ./GoF/patterns/Observer/go [seed](デフォルトseed 314)

$ go run ./GoF/patterns/Observer/go 314
observer.go
// 実行方法は main.go を参照(go run ./GoF/patterns/Observer/go [seed])。

package main

// Observer はJava版 Observer インタフェース相当。
// Subject(NumberGenerator)の状態が変わったことの通知を受け取る役。
// 一つのSubjectをObserverは複数観察してよい。
//
// Go の interface は暗黙実装(implements を書かない)。
// ここがJava版との一番わかりやすい違い(Strategy/go版のStrategyと同じ発想)。
type Observer interface {
	Update(generator NumberGenerator) // NumberGeneratorの状態変化を受け取る
}
number_generator.go
// 実行方法は main.go を参照(go run ./GoF/patterns/Observer/go [seed])。

package main

// NumberGenerator はSubject役(Java版 abstract class NumberGenerator 相当)。
//
// Java版はaddObserver/deleteObserver/notifyObserversに共通のデフォルト実装を持ち、
// getNumber/executeだけをサブクラス(RandomNumberGenerator)任せの抽象メソッドにしている。
// Goのinterfaceはメソッド本体を持てないので、共通実装の置き場所として
// baseNumberGenerator(構造体)を用意し、RandomNumberGeneratorがembeddingで借りる
// (Decorator/go の baseIceCream と同じ手筋)。
type NumberGenerator interface {
	GetNumber() int                   // 数を取得する
	Execute()                         // 数を生成する
	AddObserver(observer Observer)    // Observerを追加
	DeleteObserver(observer Observer) // Observerを削除
}

// baseNumberGenerator is where Java's addObserver/deleteObserver/notifyObservers
// live. ConcreteSubject(RandomNumberGenerator)がこれをembeddingして借りる。
type baseNumberGenerator struct {
	observers []Observer
}

func (g *baseNumberGenerator) AddObserver(observer Observer) {
	g.observers = append(g.observers, observer)
}

func (g *baseNumberGenerator) DeleteObserver(observer Observer) {
	for i, o := range g.observers {
		if o == observer {
			g.observers = append(g.observers[:i], g.observers[i+1:]...)
			return
		}
	}
}

// notifyObservers はJava版 notifyObservers() 相当。
//
// Goのembeddingでは、base構造体の中から「今embeddingしている外側の具象型」を
// 仮想呼び出しできない(FactoryMethod/go の Create(c Creator, ...) と同じ制約)。
// Java版のnotifyObservers()はthis(=動的束縛でRandomNumberGeneratorそのもの)を
// observer.update(this)へ渡すことで、Observer側からgetNumber()の最新実装を
// 呼ばせている。Goではその「this」をselfとして明示的に受け取ることで再現する。
// 呼び出し側(RandomNumberGenerator.Execute)がg.notifyObservers(g)の形で
// 自分自身を渡す。
func (g *baseNumberGenerator) notifyObservers(self NumberGenerator) {
	for _, observer := range g.observers {
		observer.Update(self)
	}
}
random_number_generator.go
// 実行方法は main.go を参照(go run ./GoF/patterns/Observer/go [seed])。

package main

import "math/rand"

// RandomNumberGenerator はConcreteSubject(Java版 RandomNumberGenerator.java 相当)。
// baseNumberGeneratorをembeddingし、add/delete Observerを借りる。
type RandomNumberGenerator struct {
	baseNumberGenerator
	random *rand.Rand // 乱数生成機
	number int        // 現在の数
}

// NewRandomNumberGenerator はコンストラクタ相当。
// Java版の `new Random()`(seedなし)と違い、シード指定可能な乱数を作る
// (Strategy/go版のNewWinningStrategy等と同じ方針)。
func NewRandomNumberGenerator(seed int64) *RandomNumberGenerator {
	return &RandomNumberGenerator{random: rand.New(rand.NewSource(seed))}
}

func (g *RandomNumberGenerator) GetNumber() int { // 数を取得する
	return g.number
}

func (g *RandomNumberGenerator) Execute() {
	for i := 0; i < 20; i++ {
		g.number = g.random.Intn(50)
		g.notifyObservers(g) // "self"としてgを渡す(number_generator.go参照)
	}
}
digit_observer.go
// 実行方法は main.go を参照(go run ./GoF/patterns/Observer/go [seed])。

package main

import (
	"fmt"
	"time"
)

// DigitObserver はConcreteObserver: 数値そのものを表示する。
type DigitObserver struct{}

func (DigitObserver) Update(generator NumberGenerator) {
	fmt.Printf("DigitObserver:%d\n", generator.GetNumber())
	// Java版はThread.sleep(100)。デモの実行時間短縮のため15msに縮めている
	// (Proxy/go版のheavyJobと同じ理由)。
	time.Sleep(15 * time.Millisecond)
}
graph_observer.go
// 実行方法は main.go を参照(go run ./GoF/patterns/Observer/go [seed])。

package main

import (
	"fmt"
	"strings"
	"time"
)

// GraphObserver はConcreteObserver: 数値をアスタリスクのグラフとして表示する。
type GraphObserver struct{}

func (GraphObserver) Update(generator NumberGenerator) {
	fmt.Println("GraphObserver:" + strings.Repeat("*", generator.GetNumber()))
	// Java版はThread.sleep(100)。デモの実行時間短縮のため15msに縮めている
	// (Proxy/go版のheavyJobと同じ理由)。
	time.Sleep(15 * time.Millisecond)
}
main.go
package main

import (
	"fmt"
	"os"
	"strconv"
)

// 実行:
//
//	go run ./GoF/patterns/Observer/go            # デフォルトseed(314)
//	go run ./GoF/patterns/Observer/go 314        # seed指定
//
// Java版Main.javaと同じお題: RandomNumberGeneratorが乱数を生成しつつ、
// DigitObserver(数値表示)とGraphObserver(アスタリスクグラフ表示)の
// 2つのObserverへ通知する。
func main() {
	seed := int64(314)
	if len(os.Args) == 2 {
		s, err := strconv.ParseInt(os.Args[1], 10, 64)
		if err != nil {
			fmt.Println("Usage: go run ./GoF/patterns/Observer/go [randomseed]")
			os.Exit(1)
		}
		seed = s
	}

	// Java版の `NumberGenerator generator = new RandomNumberGenerator();` に倣い、
	// 抽象型(interface)の変数として保持する。
	var generator NumberGenerator = NewRandomNumberGenerator(seed)
	observer1 := DigitObserver{}
	observer2 := GraphObserver{}
	generator.AddObserver(observer1)
	generator.AddObserver(observer2)
	generator.Execute()
}
PHP
index.php
<?php
ini_set("display_errors", 1);



/**
 * イベントリスナー
 * サービスを提供するオブジェクトへ
 * 指定のインタフェースを実装したオブジェクトを
 * 事前登録
 * 指定のタイミングで処理を働きかける
 * 
 * JSのDOMはだいたいこれを実装している
 * 
 */



/**
 * ラジオそのもののインタフェース
 * 発信役
 */
interface ISubject
{
  public function add($obserrer);
  public function remove($obserrer);
  public function notify($e);
}


/**
 * リスナー
 * 聞く体勢だけ持ってればいい
 * 
 */


interface IObserver
{
  public function receive($music);
}


/**
 * 実際のラジオ
 */


class Radio  implements ISubject
{
  // リスナー全員
  private $observers = array();


  /**
   * リスナーの「視聴契約」
   */
  public function add($observer)
  {
    $this->observers[] = $observer;
  }

  /**
   * リスナーの「視聴解約」
   */
  public function remove($observer)
  {
    foreach ($this->observers as $obs) {
      if ($observer != $obs) {
        $this->observers[] = $obs; //observerを上書きしてるのか?これ動くの?
      }
    }
  }

  public function notify($e)
  {
    foreach ($this->observers as $obs) {
      $obs->receive($e);
    }
  }

  public function broadCast($music)
  {
    $this->notify($music);
  }
  public function talk()
  {
    $this->notify("DJの喋り");
  }
}


/**
 * リスナー
 */


class Listenter  implements IObserver
{
  private $name;

  public function __construct($name)
  {
    $this->name = $name;
  }

  public function receive($music)
  {
    print "ラジオネーム $this->name さんが $music " . "を聞きました <br>";
  }
}


$radio = new Radio();
$radio->add(new Listenter("Larry"));
$radio->add(new Listenter("Gates"));
$radio->add(new Listenter("Jobs"));
$radio->broadCast("SHIELD TRIGGER! ~クロック8枚~");
$radio->talk();
TypeScript

TypeScriptにはJavaと同様のabstract classがあるため、NumberGeneratorはJava版の形をほぼそのまま踏襲できる(Go版のような「selfの明示渡し」は不要)。乱数はNode標準にシード指定可能なものが無いため、Strategy版と同じ自前のseedable RNG(SeededRandom、xorshift32)を使う。

実行: npx tsx GoF/patterns/Observer/typescript/main.ts [seed](デフォルトseed 314)

$ npx tsx GoF/patterns/Observer/typescript/main.ts 314
observer.ts
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

import type { NumberGenerator } from "./number_generator";

// Observer: Java版 Observer インタフェース相当。
// Subject(NumberGenerator)の状態が変わったことの通知を受け取る役。
// TypeScriptのinterfaceはJavaのinterfaceとほぼ同じ感覚で書ける
// (Go版のような暗黙実装ではなく、`implements`を明示する点はJavaと同じ)。
export interface Observer {
  update(generator: NumberGenerator): void;
}
number_generator.ts
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

import type { Observer } from "./observer";

// NumberGenerator: Subject役(Java版 abstract class NumberGenerator 相当)。
//
// Java版はaddObserver/deleteObserver/notifyObserversに共通のデフォルト実装を持ち、
// getNumber/executeだけをサブクラス(RandomNumberGenerator)任せの抽象メソッドに
// している。TypeScriptにもJavaと同様のabstractクラスがあるため
// (Composite/typescript版のEntryと同じ方針)、Java版の形をそのまま踏襲できる。
// Go版のように「selfを明示的に渡す」工夫は不要で、notifyObservers内のthisが
// そのまま動的束縛でRandomNumberGeneratorを指す。
export abstract class NumberGenerator {
  private observers: Observer[] = [];

  addObserver(observer: Observer): void {
    this.observers.push(observer);
  }

  deleteObserver(observer: Observer): void {
    this.observers = this.observers.filter((o) => o !== observer);
  }

  notifyObservers(): void {
    for (const observer of this.observers) {
      observer.update(this);
    }
  }

  abstract getNumber(): number;
  abstract execute(): void;
}
random.ts
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

// Node.jsの標準ライブラリにはシード指定可能な乱数がない(Math.randomはシード不可)。
// Strategy/typescript版のSeededRandom(xorshift32)をそのまま流用する。
export class SeededRandom {
  private state: number;
  constructor(seed: number) {
    // 0だとxorshiftが機能しないので下駄を履かせる
    this.state = (seed >>> 0) || 0x9e3779b9;
  }
  // [0, bound) の整数を返す。Java の Random#nextInt(bound) / Go の rand.Intn(bound) 相当。
  nextInt(bound: number): number {
    this.state ^= this.state << 13;
    this.state ^= this.state >>> 17;
    this.state ^= this.state << 5;
    this.state >>>= 0;
    return this.state % bound;
  }
}
random_number_generator.ts
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

import { NumberGenerator } from "./number_generator";
import { SeededRandom } from "./random";

// RandomNumberGenerator: ConcreteSubject(Java版 RandomNumberGenerator.java 相当)。
export class RandomNumberGenerator extends NumberGenerator {
  private random: SeededRandom;
  private number = 0;

  constructor(seed: number) {
    super();
    this.random = new SeededRandom(seed);
  }

  getNumber(): number {
    return this.number;
  }

  execute(): void {
    for (let i = 0; i < 20; i++) {
      this.number = this.random.nextInt(50);
      this.notifyObservers();
    }
  }
}
sleep.ts
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

// Java版のDigitObserver/GraphObserverはThread.sleep(100)で表示を少しずつ流す。
// Node.jsに同期sleepの標準APIは無く、update()はasyncにできない(Java版のupdateも
// 同期メソッド)ため、時間切れまで回すビジーウェイトで代用する
// (Proxy/typescript版のheavyJobと同じ理由・同じ手筋)。
export function sleepSyncMs(ms: number): void {
  const until = Date.now() + ms;
  while (Date.now() < until) {
    // busy wait
  }
}
digit_observer.ts
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

import type { NumberGenerator } from "./number_generator";
import type { Observer } from "./observer";
import { sleepSyncMs } from "./sleep";

// DigitObserver: ConcreteObserver。数値そのものを表示する。
export class DigitObserver implements Observer {
  update(generator: NumberGenerator): void {
    console.log(`DigitObserver:${generator.getNumber()}`);
    // Java版はThread.sleep(100)。デモの実行時間短縮のため15msに縮めている
    // (Proxy/typescript版のheavyJobと同じ理由)。
    sleepSyncMs(15);
  }
}
graph_observer.ts
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

import type { NumberGenerator } from "./number_generator";
import type { Observer } from "./observer";
import { sleepSyncMs } from "./sleep";

// GraphObserver: ConcreteObserver。数値をアスタリスクのグラフとして表示する。
export class GraphObserver implements Observer {
  update(generator: NumberGenerator): void {
    console.log(`GraphObserver:${"*".repeat(generator.getNumber())}`);
    // Java版はThread.sleep(100)。デモの実行時間短縮のため15msに縮めている
    // (Proxy/typescript版のheavyJobと同じ理由)。
    sleepSyncMs(15);
  }
}
main.ts
// Observer パターン: 乱数を生成しつつ、複数のObserverに通知する (Java版と同じお題)
//
// 実行:
//   npx tsx GoF/patterns/Observer/typescript/main.ts            # デフォルトseed(314)
//   npx tsx GoF/patterns/Observer/typescript/main.ts 314        # seed指定
//
// 乱数はStrategy/typescript版に倣い、Node標準にシード指定可能な乱数がないため
// 自前のseedable RNG(SeededRandom、xorshift32)を使う(random.ts参照)。

import { DigitObserver } from "./digit_observer";
import { GraphObserver } from "./graph_observer";
import { NumberGenerator } from "./number_generator";
import { RandomNumberGenerator } from "./random_number_generator";

function main(): void {
  const args = process.argv.slice(2);
  let seed = 314;
  if (args.length === 1) {
    seed = Number.parseInt(args[0], 10);
    if (Number.isNaN(seed)) {
      console.log("Usage: npx tsx main.ts [randomseed]");
      process.exit(1);
    }
  }

  const generator: NumberGenerator = new RandomNumberGenerator(seed);
  const observer1 = new DigitObserver();
  const observer2 = new GraphObserver();
  generator.addObserver(observer1);
  generator.addObserver(observer2);
  generator.execute();
}

main();
Python

NumberGenerator(ABC)にJava版と同じ形で共通実装(add/delete/notify)を持たせ、RandomNumberGeneratorがこれを継承する。ObserverNumberGeneratorが互いの型を参照し合う(update(generator)notify_observersが相手の型を要求する)ため、循環importを避けてTYPE_CHECKING配下の前方参照にしている。

実行: python3 GoF/patterns/Observer/python/main.py [seed](デフォルトseed 314)

$ python3 GoF/patterns/Observer/python/main.py 314
observer.py
"""Observer パターン: Observer役 (Java版の Observer.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。

PythonにはJavaのinterfaceに相当する言語機能はないため、Composite/Bridge版と同じ方針で
抽象基底クラス(ABC)を使う。update()の引数型はnumber_generator.pyとの循環import
(number_generator.pyもこちらのObserverをimportする)を避けるため、TYPE_CHECKING配下の
前方参照にしている。実行時にはこちらのimportは走らず、型検査専用の参照になる。
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from number_generator import NumberGenerator


class Observer(ABC):
    @abstractmethod
    def update(self, generator: "NumberGenerator") -> None: ...
number_generator.py
"""Observer パターン: Subject役 (Java版の NumberGenerator.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。

Java版のNumberGeneratorは抽象クラスで、addObserver/deleteObserver/notifyObserversに
共通のデフォルト実装を持ち、getNumber/executeだけをサブクラス(RandomNumberGenerator)
任せの抽象メソッドにしている。PythonにはJavaのinterfaceに相当する言語機能はないため、
Composite/Bridge版と同じ方針で抽象基底クラス(ABC)を使う。TypeScript版と同じく、
Java版そのままの継承(RandomNumberGeneratorがこれを継承)で書ける。
Go版のように「selfを明示的に渡す」工夫は不要で、notify_observers内のselfが
そのまま動的束縛でRandomNumberGeneratorを指す。
"""

from __future__ import annotations

from abc import ABC, abstractmethod

from observer import Observer


class NumberGenerator(ABC):
    def __init__(self) -> None:
        self._observers: list[Observer] = []

    def add_observer(self, observer: Observer) -> None:
        self._observers.append(observer)

    def delete_observer(self, observer: Observer) -> None:
        # Java版のArrayList#removeは要素が無ければ何もせずfalseを返すだけで例外を投げない。
        # list.remove()はValueErrorを投げてしまうため、Java版と同じ「未登録なら何もしない」
        # 挙動に合わせる(Go/TS版も同様に無条件で無視する実装になっている)。
        if observer in self._observers:
            self._observers.remove(observer)

    def notify_observers(self) -> None:
        for observer in self._observers:
            observer.update(self)

    @abstractmethod
    def get_number(self) -> int: ...

    @abstractmethod
    def execute(self) -> None: ...
random_number_generator.py
"""Observer パターン: ConcreteSubject (Java版の RandomNumberGenerator.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""

from __future__ import annotations

import random

from number_generator import NumberGenerator


class RandomNumberGenerator(NumberGenerator):
    def __init__(self, seed: int) -> None:
        super().__init__()
        self._random = random.Random(seed)  # 乱数生成機(シード指定可能)
        self._number = 0  # 現在の数

    def get_number(self) -> int:
        return self._number

    def execute(self) -> None:
        for _ in range(20):
            self._number = self._random.randrange(50)
            self.notify_observers()
digit_observer.py
"""Observer パターン: ConcreteObserver (Java版の DigitObserver.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。
数値そのものを表示する。
"""

from __future__ import annotations

import time
from typing import TYPE_CHECKING

from observer import Observer

if TYPE_CHECKING:
    from number_generator import NumberGenerator


class DigitObserver(Observer):
    def update(self, generator: "NumberGenerator") -> None:
        print(f"DigitObserver:{generator.get_number()}")
        # Java版はThread.sleep(100)。デモの実行時間短縮のため15msに縮めている
        # (Proxy/python版のheavy_jobと同じ理由)。
        time.sleep(0.015)
graph_observer.py
"""Observer パターン: ConcreteObserver (Java版の GraphObserver.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。
数値をアスタリスクのグラフとして表示する。
"""

from __future__ import annotations

import time
from typing import TYPE_CHECKING

from observer import Observer

if TYPE_CHECKING:
    from number_generator import NumberGenerator


class GraphObserver(Observer):
    def update(self, generator: "NumberGenerator") -> None:
        print(f"GraphObserver:{'*' * generator.get_number()}")
        # Java版はThread.sleep(100)。デモの実行時間短縮のため15msに縮めている
        # (Proxy/python版のheavy_jobと同じ理由)。
        time.sleep(0.015)
main.py
"""Observer パターン: 乱数を生成しつつ、複数のObserverに通知する (Java版と同じお題)

実行:
    python3 main.py            # デフォルトseed(314)
    python3 main.py 314        # seed指定
    (もしくはリポジトリルートから python3 GoF/patterns/Observer/python/main.py)
"""

from __future__ import annotations

import sys

from digit_observer import DigitObserver
from graph_observer import GraphObserver
from random_number_generator import RandomNumberGenerator


def main() -> None:
    seed = 314
    if len(sys.argv) == 2:
        try:
            seed = int(sys.argv[1])
        except ValueError:
            print("Usage: python3 main.py [randomseed]")
            sys.exit(1)

    generator = RandomNumberGenerator(seed)
    observer1 = DigitObserver()
    observer2 = GraphObserver()
    generator.add_observer(observer1)
    generator.add_observer(observer2)
    generator.execute()


if __name__ == "__main__":
    main()