Observer Pattern

觀眾者模式的定義

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

定義物件之間的一對多關係,使得一個物件改變狀態時,所有倚賴於它的物件都會得到通知並且自動被更新。

Class Diagram

PlantUML code

interface Subject {
  + {abstract} attach(o: Observer)
  + {abstract} detach(o: Observer)
  + {abstract} notify()
}
interface Observer {
  + {abstract} update()
}

class ConcreteSubject
class ConcreteObserver

Subject -> Observer : - observers
Subject <|-- ConcreteSubject
Observer <|-- ConcreteObserver
ConcreteSubject <- ConcreteObserver : - subject
  • Subject 被觀察者,它必需要能夠動態的增加、取消和通知觀察者,通常是抽象類別或實作類別
  • Observer 觀察者,在接收到被觀察者的通知時,對接受到的訊息做處理
  • ConcreteSubject 具體的被觀察者,定義自己的業務邏輯,並定義對哪些事件做通知
  • ConcreteObserver 具體的觀察者,每個觀察者在收到消息後的處理回應是不同的,各自有各自的邏輯

貓只要看到狗生氣,就會害怕逃走。

先依場景寫出介面和實作:

// 貓的介面
interface ICat
{
    // 貓只要遇到危險就會逃跑
    public function run();
}

// 貓的實作
class Cat implements ICat
{
    public function run()
    {
        echo '塊陶啊!!';
    }
}

// 狗的介面
interface IDog
{
    // 狗只要不爽就會生氣
    public function angry();
}

// 狗的實作
class Dog implements IDog
{
    private $cat;

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

    public function angry()
    {
        echo '林北今天每送!!';
        $cat->run();
    }
}

類別間的 UML 圖:

<uml>
Interface ICat {
  + {abstract} run()
}
Interface IDog {
  + {abstract} angry()
}
class Cat
class Dog
ICat <|..Cat
IDog <|.. Dog
Dog o- Cat
</uml>

場景實作如下

// 會逃跑的貓
$cat = new Cat();
// 會生氣的狗
$dog = new Dog($cat);
// 狗生氣了
$dog->angry();

程式是完成了,結果也是正確的,但是可以思考幾個問題:

  • 如果今天兔子看到狗生氣的時候會跳起來 Jump,程式該怎麼改?
  • 如果今天有第二隻小貓看到狗生氣也會逃跑,程式該怎麼改?
  • 也有可能貓和兔子都沒看到狗生氣,所以不會被嚇到,程式該怎麼改?

首先解決第一個問題:兔子看到狗生氣的時候會跳起來。

其實可以發現,貓跟兔子看到狗生氣,都會去做特定的一件事 doSomething

所以可以把程式改成這樣:

// 動物的介面
interface IAnimal
{
    // 動物只要遇到危險就會做某件事
    public function doSomething();
}

// 貓的實作
class Cat implements IAnimal
{
    public function doSomething()
    {
        echo '塊陶啊!!';
    }
}

// 兔子的實作
class Rabbit implements IAnimal
{
    public function doSomething()
    {
        echo '跳高高!!';
    }
}

// 狗的介面
interface IDog
{
    // 狗只要不爽就會生氣
    public function angry();
}

// 狗的實作
class Dog implements IDog
{
    private $animal;

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

    public function angry()
    {
        echo '林北今天每送!!';
        $animal->doSomething();
    }
}

UML 圖如下:

<uml>
Interface IAnimal {
  + {abstract} doSometing()
}
Interface IDog {
  + {abstract} angry()
}
class Rabbit
class Cat
class Dog
IAnimal <|.. Cat
IAnimal <|.. Rabbit
IDog <|.. Dog
Dog o- IAnimal
</uml>

場景實作

// 會逃跑的貓
$cat = new Cat();
// 嚇貓的狗
$catScaredByDog = new Dog($cat);
// 狗生氣了
$catScaredByDog->angry();

// 會跳高高的兔子
$rabbit = new Rabbit();
// 嚇兔子的狗
$rabbitScaredByDog = new Dog($rabbit);
// 狗生氣了
$rabbitScaredByDog ->angry();

目前改成這樣,那未來再把十二生肖都加進來都不會是問題了。

再來解決第二個問題:有第二隻小貓看到狗生氣也會逃跑。所以狗必須要具備能一次能嚇很多隻動物的介面

兩個方法:

  • 在建構子決定好要嚇哪些動物
  • 執行時期決定要嚇哪些動物

方法 1,程式碼修改如下:

// 狗的實作
class Dog implements IDog
{
    private $animals;

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

    public function angry()
    {
        echo '林北今天每送!!';
        foreach ($this->animals as $animal) {
            $animal->doSomething();
        }
    }
}

UML 圖如下:

Interface IAnimal { + {abstract} doSometing() } Interface IDog { + {abstract} angry() } class Rabbit class Cat class Dog IAnimal <|.. Cat IAnimal <|.. Rabbit IDog <|.. Dog Dog o- IAnimal

場景實作如下:

// 會逃跑的貓
$cat = new Cat();
// 會跳高高的兔子
$rabbit = new Rabbit();

// 嚇動物的狗
$dog = new Dog(array($cat, $rabbit));
// 狗生氣了
$dog->angry();

方法 2,程式碼修改如下:

這同時也解決了問題三:也有可能貓和兔子都沒看到狗生氣,所以不會被嚇到。

// 狗的介面
interface IDog
{
    // 你今天想嚇誰
    public function addAnimal(IAnimal $animal);
    // 狗只要不爽就會生氣
    public function angry();
}

// 狗的實作
class Dog implements IDog
{
    private $animals = array();

    public function addAnimal(IAnimal $animal)
    {
        $this->animals[] = $animal;
    }

    public function angry()
    {
        echo '林北今天每送!!';
        foreach ($this->animals as $animal) {
            $animal->doSomething();
        }
    }
}

UML 圖如下:

Interface IAnimal { + {abstract} doSometing() } Interface IDog { + {abstract} addAnimal(IAnimal $animal) + {abstract} angry() } class Rabbit class Cat class Dog IAnimal <|.. Cat IAnimal <|.. Rabbit IDog <|.. Dog IDog -> IAnimal

場景實作如下:

// 會逃跑的貓
$cat = new Cat();
// 會跳高高的兔子
$rabbit = new Rabbit();

// 嚇動物的狗
$dog = new Dog();
// 今天想嚇貓
$dog->addAnimal($cat);
// 今天想嚇兔子
$dog->addAnimal($rabbit);
// 狗生氣了
$dog->angry();