观察者模式

类图]]
觀察者模式(),是一種軟體設計模式。在此種模式中,一個目標物件管理所有相依於它的觀察者物件,並且在它本身的狀態改變時主動發出通知。這通常透過呼叫各觀察者所提供的方法來實現。此種模式通常被用在即時事件處理系統。

结构
在上面的UML类图中,Subject类不直接更新依赖对象的状态,Subject转而提及Observer接口(update())来更新状态,这使得Subject独立于依赖对象的状态是如何更新的。Observer1和Observer2通过将自己的同步于主题的状态实现了Observer接口。

UML序列图展示了运行时交互:Observer1和Observer2对象调用attach(this)于Subject1之上来注册自身。假定Subject1的状态变更了,Subject1调用notify()于自身之上。notify()调用update()在已注册的Observer1和Observer2对象之上,它从Subject1要来变更了的数据(getState())用于更新(同步)自身的状态。

參與者
參與本模式的各類別列出如下。成員函式以模擬的方式列出。

抽象目標類別,提供一個界面讓觀察者進行添附與解附作業。此類別內有個不公開的觀察者串鍊,並透過下列函式(方法)進行作業:

  • 添附(Attach):新增觀察者到串鍊內,以追蹤目標物件的變化。
  • 解附(Detach):將已經存在的觀察者從串鍊中移除。
  • 通知(Notify):利用觀察者所提供的更新函式來通知此目標已經產生變化。

添附函式包涵了一個觀察者物件參數。也許是觀察者類別的虛擬函式(即更新函式),或是在非物件導向的設定中所使用的函式指標(更廣泛來講,函式子或是函式物件)。

目標類別,提供了觀察者欲追蹤的狀態。也利用其源類別(例如前述的抽象目標類別)所提供的方法,來通知所有的觀察者其狀態已經更新。此類別擁有以下函式:

  • 取得狀態(GetState):回傳該目標物件的狀態。

抽象觀察者介面,是一個必須被實做的抽象類別。這個類別定義了所有觀察者都擁有的更新用介面,此介面是用來接收目標類別所發出的更新通知。此類別含有以下函式:

  • 更新(Update):會被實做的一個抽象(虛擬)函式。

抽象觀察者介面,含有指向目標類別的參考(reference),以接收來自目標類別的更新狀態。此類別含有以下函式:

  • 更新(Update):是前述抽象函式的實做。當這個函式被目標物件呼叫時,觀察者物件將會呼叫目標物件的取得狀態函式,來其所擁有的更新目標物件資訊。

每個觀察者類別都要實做它自己的更新函式,以應對狀態更新的情形。

當目標物件改變時,會通過呼叫它自己的通知函式來將通知送給每一個觀察者物件,這個通知函式則會去呼叫已經添附在串鍊內的觀察者更新函式。通知與更新函式可能會有一些參數,好指明是目前目標物件內的何種改變。這麼作將可增進觀察者的效率(只更新那些改變部份的狀態)。

用途

  • 當抽象個體有兩個互相依賴的層面時。封裝這些層面在單獨的物件內將可允許程式設計師單獨地去變更與重複使用這些物件,而不會產生兩者之間交互的問題。
  • 當其中一個物件的變更會影響其他物件,卻又不知道多少物件必須被同時變更時。
  • 當物件應該有能力通知其他物件,又不應該知道其他物件的實做細節時。

觀察者模式通常與MVC範式有關係。在MVC中,觀察者模式被用來降低model與view的耦合程度。一般而言,model的改變會觸發通知其他身為觀察者的model。而這些model實際上是view。Java Swing就是個範例,示意了model 預期會透過PropertyChangeNotification架構以送出改變的通知給其他view。Model類別是Java bean類別的一員,並擁有與上述目標類別同樣的行為。View類別則繫結了一些GUI中的可視元素,並擁有與上述觀察者類別同樣的行為。當應用程式在執行時。使用者將因view做出相應的更新而看見model所產生的變更。

示例
C++
下面是C++23实现:

import std;

using std::reference_wrapper;
using std::vector;

class Subject; // Forward declaration for usage in Observer

class Observer {
private:
// Reference to a Subject object to detach in the destructor
Subject& subject;
public:
explicit Observer(Subject& subj):
subject{subj} {
subject.attach(*this);
}

virtual ~Observer() {
subject.detach(*this);
}

Observer(const Observer&) = delete;
Observer& operator=(const Observer&) = delete;

virtual void update(Subject& s) const = 0;
};

// Subject is the base class for event generation
class Subject {
private:
vector observers;
public:
using RefObserver = reference_wrapper;

// Notify all the attached observers
void notify() {
for (const Observer& x: observers) {
x.get().update(*this);
}
}

// Add an observer
void attach(const Observer& observer) {
observers.push_back(observer);
}

// Remove an observer
void detach(Observer& observer) {
observers.remove_if(&observer -> bool {
return &obj.get() == &observer;
});
}
};

// Example of usage
class ConcreteObserver: public Observer {
public:
explicit ConcreteObserver(Subject& subj):
Observer(subj) {}

// Get notification
void update(Subject&) const override {
std::println("Got a notification");
}
};

int main(int argc, char* argv[]) {
Subject cs;
ConcreteObserver co1(cs);
ConcreteObserver co2(cs);
cs.notify();
}

程序的输出为:

Got a notification
Got a notification

Java
下面的Java例子接受键盘输入并将每个输入行作为一个事件来处理。当在一个字符串由System.in提供出来之时,方法notifyObservers()接着被调用,用来通知所有观察者这个事件发生了,这采用了调用它们的更新方法的形式。

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

interface Observer {
void update(String event);
}

class EventSource {
List observers = new ArrayList<>();

public void notifyObservers(String event) {
observers.forEach(observer -> observer.update(event));
}

public void addObserver(Observer observer) {
observers.add(observer);
}

public void scanSystemIn() {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
notifyObservers(line);
}
}
}

public class ObserverDemo {
public static void main(String[] args) {
System.out.println("Enter Text: ");
EventSource eventSource = new EventSource();

eventSource.addObserver(event -> System.out.println("Received response: " + event));

eventSource.scanSystemIn();
}
}

C#
C#提供了和接口,还有如何实现这个设计模式的文档。

class Payload {
internal string Message { get; init; }
}

class Subject : IObservable {
private readonly List _observers = new List();

IDisposable IObservable.Subscribe(IObserver observer) {
if (!_observers.Contains(observer)) {
_observers.Add(observer);
}

return new Unsubscriber(observer, _observers);
}

internal void SendMessage(string message) {
foreach (var observer in _observers) {
observer.OnNext(new Payload { Message = message });
}
}
}

internal class Unsubscriber : IDisposable {
private readonly IObserver _observer;
private readonly ICollection _observers;

internal Unsubscriber(
IObserver observer,
ICollection observers) {
_observer = observer;
_observers = observers;
}

void IDisposable.Dispose() {
if (_observer != null && _observers.Contains(_observer)) {
_observers.Remove(_observer);
}
}
}

internal class Observer : IObserver {
private string _message;

public void OnCompleted() {
}

public void OnError(Exception error) {
}

public void OnNext(Payload value) {
_message = value.Message;
}

internal IDisposable Register(IObservable subject) {
return subject.Subscribe(this);
}
}

Python
下面是Python的例子:

from abc import ABC, abstractmethod
from weakref import WeakValueDictionary

class Subject():
def __init__(self):
self.__observer_dict = WeakValueDictionary()
def register(self, observer):
self.__observer_dict[id(observer)] = observer
def unregister(self, observer):
del self.__observer_dict[id(observer)]
def notify(self, args, *kwargs):
for key in self.__observer_dict:
self.__observer_dict[key].update(self, args, *kwargs)

class Observer(ABC):
def __init__(self, subject):
subject.register(self)
@abstractmethod
def update(self): pass

class ObserverA(Observer):
def update(self, subject, args, *kwargs):
print("Got", args, kwargs, "From", subject)

其执行:

>> subject = Subject() >> observer1 = ObserverA(subject) >> observer2 = ObserverA(subject) >> subject.notify("test", kw="text")

Got ('test',) {'kw': 'text'} From
Got ('test',) {'kw': 'text'} From

参见
*中介者模式

引用

评论 (0)

  • 还没有评论,来抢沙发吧。