观察者模式(行为型模式)
模式概述
观察者模式是一种行为设计模式,它允许通过一种订阅机制,在对象事件发生时向多个“观察”该对象的其他对象发出通知。
问题
假如李星云和姬如雪逛了一天街逛累了,想找个吃晚饭的地方歇歇脚,然后再去看一场电影。但当他们到离得最近的餐馆时,发现全都是在等着吃饭的人。
他们现在有两种选择:要么在一直在这里排队等着,要么先去饿着肚子看电影。
方案
那些可以将自身状态的改变通知给其他对象的对象,称之为发布者(Publisher),而所有关注发布者状态变化的对象则被称为订阅者(Subscribers)。
观察者模式建议为发布者类添加订阅机制,让每个订阅者都能订阅或取消订阅发布者的事件流。
如果餐馆老板开发了一个排队小程序,李星云只需要关注这个小程序并拿到排队号码,那么当队列发生变化时,小程序就会向所有顾客发出通知,顾客不必到现场排队也能知道是否轮到自己就餐了。
现在,李星云拿到了排队就餐号码。他在想,还有一会时间,是否需要再去逛一逛,还是先去看电影。
结构

实现
Java
/**
* 发布者
*
*/
public class Publisher {
Map<String, List<Listener>> listeners = new HashMap<>();
public Publisher(String... operations) {
for (String operation : operations) {
this.listeners.put(operation, new ArrayList<>());
}
}
public void subscribe(String eventType, Listener listener) {
List<Listener> users = listeners.get(eventType);
users.add(listener);
}
public void unsubscribe(String eventType, Listener listener) {
List<Listener> users = listeners.get(eventType);
users.remove(listener);
}
public void notify(String eventType, File file) {
List<Listener> users = listeners.get(eventType);
for (Listener listener : users) {
listener.update(eventType, file);
}
}
}
/**
* 具体发布者
*
*/
public class Editor {
public Publisher events;
private File file;
public Editor() {
this.events = new Publisher("open", "save");
}
public void openFile(String filePath) {
this.file = new File(filePath);
events.notify("open", file);
}
public void saveFile() throws Exception {
if (this.file != null) {
events.notify("save", file);
} else {
throw new Exception("Please open a file first.");
}
}
}
/**
* 观察者接口
*
*/
public interface Listener {
void update(String eventType, File file);
}
/**
* 具体观察者
*
*/
public class EmailNotificationListener implements Listener {
private String email;
public EmailNotificationListener(String email) {
this.email = email;
}
@Override
public void update(String eventType, File file) {
System.out.println("发送邮件通知:" + file.getName());
}
}
/**
* 具体观察者
*
*/
public class LogOpenListener implements Listener {
private File log;
public LogOpenListener(String fileName) {
this.log = new File(fileName);
}
@Override
public void update(String eventType, File file) {
System.out.println("保存日志通知:" + file.getName());
}
}
/**
* 客户端
*
*/
public class Client {
public static void main(String[] args) {
Editor editor = new Editor();
editor.events.subscribe("open", new LogOpenListener("/path/to/log/logger.log"));
editor.events.subscribe("save", new EmailNotificationListener("admin@example.com"));
try {
editor.openFile("test.txt");
editor.saveFile();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Go
package main
import "fmt"
/**
* 观察主体接口
*
*/
type Subject interface {
register(observer Observer)
deregister(observer Observer)
notifyAll()
}
/**
* 具体主体
*
*/
type Item struct {
observerList []Observer
name string
inStock bool
}
func newItem(name string) *Item {
return &Item{
name: name,
}
}
func (i *Item) updateAvailability() {
fmt.Printf("商品 %s 缺货中\n", i.name)
i.inStock = true
i.notifyAll()
}
func (i *Item) register(o Observer) {
i.observerList = append(i.observerList, o)
}
func (i *Item) deregister(o Observer) {
i.observerList = removeFromslice(i.observerList, o)
}
func (i *Item) notifyAll() {
for _, observer := range i.observerList {
observer.update(i.name)
}
}
func removeFromslice(observerList []Observer, observerToRemove Observer) []Observer {
observerListLength := len(observerList)
for i, observer := range observerList {
if observerToRemove.getID() == observer.getID() {
observerList[observerListLength-1], observerList[i] = observerList[i], observerList[observerListLength-1]
return observerList[:observerListLength-1]
}
}
return observerList
}
/**
* 观察者接口
*
*/
type Observer interface {
update(string)
getID() string
}
/**
* 具体观察者
*
*/
type Customer struct {
id string
}
func (c *Customer) update(itemName string) {
fmt.Printf("给顾客 %s 发送邮件,%s 已到货\n", c.id, itemName)
}
func (c *Customer) getID() string {
return c.id
}
/**
* 客户端
*
*/
func main() {
shirtItem := newItem("耐克衬衣")
observerFirst := &Customer{id: "abc@gmail.com"}
observerSecond := &Customer{id: "xyz@gmail.com"}
shirtItem.register(observerFirst)
shirtItem.register(observerSecond)
shirtItem.updateAvailability()
}
适用场景
当一个对象的改变需要通知其他一个或多个对象,且不知道具体对象有哪些时,可以使用观察者模式。
当需要在系统中创建一个触发链时,例如,A对象的行为影响B对象,B对象的行为影响C对象……,可以使用观察者模式。
优缺点
观察者模式的优点。
无需修改发布者代码就能引入新的订阅者类,符合
开闭原则
。在观察目标和观察者之间建立一个抽象的耦合。
观察者模式的缺点。
如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
相关性
责任链模式
、命令模式
、中介者模式
和观察者模式
都用于处理请求发送者和接收者之间的不同连接方式。中介者模式
和观察者模式
之间的区别往往很难记住,这是因中介者模式
消除了一系列系统组件之间的相互依赖,因为这些组件只依赖于同一个中介者对象。而有些中介者模式
是通过观察者模式
实现的:中介者对象担当发布者的角色,其他组件则作为订阅者,当中介者以这种方式实现时,它看上去与观察者非常相似。
感谢支持
更多内容,请移步《超级个体》。