Observer
状態の変化を通知する
概要
別名Pub-Sub(Publish-Subscribe)パターン
- Publish: 発行
- Subscribe: 購読
マンガでわかる Observer
マンガでわかる Observer #デザインパターン - Qiita
でざぱたんで覚える Observer
ちびキャラは「オブザーバーたん」。疎開先で仲間外れにされ、ひとりで山や川を眺めて過ごす少女。「声をあげなきゃ、そこに居たことすら覚えていてくれない」——どれだけ見ていても、先に自分を登録(=声をあげる)した者だけが通知の輪に入れる、というイベントリスナー登録の関係を孤独として描く。登録の仕組みを隠蔽しないと依存が強まるのでMediator併用が良い、という補足まで載っている。
出典: いしだけ『でざぱたん: ちびキャラで覚えるデザインパターン』(P.119〜)
登場人物
- Subject: 観察される側のインタフェース
- Observerに自身の変化をnotifyする
- ConcreteSubject
- Observer: 監視役。Subjectから「状態が変わりましたよ」と通知を受けるインタフェース
- 一つのSubjectを監視するObserverは複数存在して良い
- ConcreteObserver
クラス図

このサイトの実装(乱数生成の通知)での対応関係:
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
public interface Observer {
public abstract void update(NumberGenerator generator);
}
public class DigitObserver implements Observer {
public void update(NumberGenerator generator) {
System.out.println("DigitObserver:" + generator.getNumber());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
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) {
}
}
}
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(); // 数を生成する
}
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)
// 実行方法は 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の状態変化を受け取る
}
// 実行方法は 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)
}
}
// 実行方法は 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参照)
}
}
// 実行方法は 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)
}
// 実行方法は 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)
}
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
<?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)
// 単体では実行不可。エントリポイントは 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;
}
// 単体では実行不可。エントリポイントは 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;
}
// 単体では実行不可。エントリポイントは 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;
}
}
// 単体では実行不可。エントリポイントは 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();
}
}
}
// 単体では実行不可。エントリポイントは 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
}
}
// 単体では実行不可。エントリポイントは 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);
}
}
// 単体では実行不可。エントリポイントは 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);
}
}
// 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がこれを継承する。ObserverとNumberGeneratorが互いの型を参照し合う(update(generator)とnotify_observersが相手の型を要求する)ため、循環importを避けてTYPE_CHECKING配下の前方参照にしている。
実行: python3 GoF/patterns/Observer/python/main.py [seed](デフォルトseed 314)
"""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: ...
"""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: ...
"""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()
"""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)
"""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)
"""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()