觀眾者模式的定義
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
定義物件之間的一對多關係,使得一個物件改變狀態時,所有倚賴於它的物件都會得到通知並且自動被更新。
UML
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 : - subjectTerms
- Subject 被觀察者,它必需要能夠動態的增加、取消和通知觀察者,通常是抽象類別或實作類別
- Observer 觀察者,在接收到被觀察者的通知時,對接受到的訊息做處理
- ConcreteSubject 具體的被觀察者,定義自己的業務邏輯,並定義對哪些事件做通知
- ConcreteObserver 具體的觀察者,每個觀察者在收到消息後的處理回應是不同的,各自有各自的邏輯
Example
貓只要看到狗生氣,就會害怕逃走。
Before using pattern
先依場景寫出介面和實作:
// 貓的介面
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();Problems
程式是完成了,結果也是正確的,但是可以思考幾個問題:
- 如果今天兔子看到狗生氣的時候會跳起來
Jump,程式該怎麼改? - 如果今天有第二隻小貓看到狗生氣也會逃跑,程式該怎麼改?
- 也有可能貓和兔子都沒看到狗生氣,所以不會被嚇到,程式該怎麼改?
After using pattern 1
首先解決第一個問題:兔子看到狗生氣的時候會跳起來。
其實可以發現,貓跟兔子看到狗生氣,都會去做特定的一件事 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();目前改成這樣,那未來再把十二生肖都加進來都不會是問題了。
After using pattern 2
再來解決第二個問題:有第二隻小貓看到狗生氣也會逃跑。所以狗必須要具備能一次能嚇很多隻動物的介面
兩個方法:
- 在建構子決定好要嚇哪些動物
- 執行時期決定要嚇哪些動物
方法 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 圖如下:
場景實作如下:
// 會逃跑的貓
$cat = new Cat();
// 會跳高高的兔子
$rabbit = new Rabbit();
// 嚇動物的狗
$dog = new Dog(array($cat, $rabbit));
// 狗生氣了
$dog->angry();After using pattern 3
方法 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 圖如下:
場景實作如下:
// 會逃跑的貓
$cat = new Cat();
// 會跳高高的兔子
$rabbit = new Rabbit();
// 嚇動物的狗
$dog = new Dog();
// 今天想嚇貓
$dog->addAnimal($cat);
// 今天想嚇兔子
$dog->addAnimal($rabbit);
// 狗生氣了
$dog->angry();