设计模式之观察者模式
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到并自动更新。
来解析这个定义:
- “一对多依赖”说明被观察的主题需要维护一个观察者列表,当然要提供加入列表和从列表删除的能力,可以命名为attach、detach方法,来实现观察者的添加和移除。
- “对象改变状态”说明了被观察者有一个被关注的参数属性。
- “一个对象改变状态时,它的所有依赖者都会收到并自动更新”说明当主题发生变化时,依赖者有一个方法可以被触发执行更新操作,所谓“自动”指依赖者不必要主动调用,而是被动调用,因此依赖者需要有一个被调用的方法,可以命名为update。
- 结合关联抽象的经验,所有依赖者应该有一个共同的抽象,给到主题维护
可以初步得到以下类图。

学习设计模式,一个关键的自我校验,是能够自己给自己出题,构建场景,并通过目标设计模式来解决。
- 定义观察者接口(Observer)
// 观察者接口
public interface Subscriber {
void update(String news); // 更新方法,接收新闻内容
}
- 定义被观察者(Subject)
import java.util.ArrayList;
import java.util.List;
// 被观察者(新闻发布者)
public class NewsPublisher {
private List<Subscriber> subscribers = new ArrayList<>(); // 订阅者列表
private String latestNews; // 最新新闻
// 添加订阅者
public void attach(Subscriber subscriber) {
subscribers.add(subscriber);
}
// 移除订阅者
public void detach(Subscriber subscriber) {
subscribers.remove(subscriber);
}
// 通知所有订阅者
public void notifySubscribers(String news) {
for (Subscriber subscriber : subscribers) {
subscriber.update(news);
}
}
// 发布新闻
public void publishNews(String news) {
this.latestNews = news;
System.out.println("新闻发布者发布了新新闻: " + news);
notifySubscribers(news); // 通知所有订阅者
}
}
- 实现具体观察者(Concrete Observer)
// 具体观察者(订阅者)
public class EmailSubscriber implements Subscriber {
private String name;
public EmailSubscriber(String name) {
this.name = name;
}
@Override
public void update(String news) {
System.out.println(name + " 收到新新闻: " + news);
}
}
- 测试代码
public class ObserverPatternDemo {
public static void main(String[] args) {
// 创建新闻发布者
NewsPublisher publisher = new NewsPublisher();
// 创建订阅者
Subscriber subscriber1 = new EmailSubscriber("订阅者A");
Subscriber subscriber2 = new EmailSubscriber("订阅者B");
// 订阅新闻
publisher.attach(subscriber1);
publisher.attach(subscriber2);
// 发布新闻
publisher.publishNews("Java 17 正式发布!");
publisher.publishNews("人工智能技术新突破!");
// 取消订阅
publisher.detach(subscriber2);
// 再次发布新闻
publisher.publishNews("观察者模式详解!");
}
}
至此观察者模式的实例已经展示出来了,有没有发现,Subject是一个具体的类,而不是一个抽象,在观察者模式中,Subject 是否应该设计为抽象类或接口,取决于具体的应用场景和设计需求。
- Subject作为具体类的场景,就是上面的示例,这种场景中,Subject的行为是固定的,且不需要扩展和修改,可以直接将其设计为具体类,方便快速实现。
- Subject 作为抽象类或接口的场景,如果 Subject 的行为需要支持多种实现或扩展,应该将其设计为抽象类或接口。例如有多个不同的新闻发布者,或者未来可能增加新的通知方式,就可以通过抽象来与具体类进行解耦。先看看接口实现。

// Subject 作为接口
public interface Subject {
void attach(Subscriber subscriber);
void detach(Subscriber subscriber);
void notifySubscribers(String news);
}
// 具体 Subject 实现
public class NewsPublisher implements Subject {
private List<Subscriber> subscribers = new ArrayList<>();
@Override
public void attach(Subscriber subscriber) {
subscribers.add(subscriber);
}
@Override
public void detach(Subscriber subscriber) {
subscribers.remove(subscriber);
}
@Override
public void notifySubscribers(String news) {
for (Subscriber subscriber : subscribers) {
subscriber.update(news);
}
}
public void publishNews(String news) {
System.out.println("发布新闻: " + news);
notifySubscribers(news);
}
}
- Subject 作为抽象类的场景,与接口的区别是,多个Subject有可以共享的部分逻辑,例如维护订阅者列表。

// Subject 作为抽象类
public abstract class Subject {
private List<Subscriber> subscribers = new ArrayList<>();
public void attach(Subscriber subscriber) {
subscribers.add(subscriber);
}
public void detach(Subscriber subscriber) {
subscribers.remove(subscriber);
}
public void notifySubscribers(String news) {
for (Subscriber subscriber : subscribers) {
subscriber.update(news);
}
}
public abstract void publishNews(String news); // 子类实现具体发布逻辑
}
// 具体 Subject 实现
public class NewsPublisher extends Subject {
@Override
public void publishNews(String news) {
System.out.println("发布新闻: " + news);
notifySubscribers(news);
}
}
那么何时使用抽象类或接口。
- 使用接口:
- 当需要支持多继承时(Java 中类只能单继承,但可以实现多个接口)。
- 当 Subject 的行为完全由子类决定时。
- 当希望将 Subject 的定义与实现完全解耦时。
- 使用抽象类:
- 当多个 Subject 实现共享部分逻辑时。
- 当需要为子类提供默认实现时。
- 当 Subject 的行为部分固定、部分需要扩展时。
在实际生产环境中,某些观察者只对特定类型的新闻感兴趣(例如科技新闻、体育新闻),可以改进设计以支持更细粒度的通知。如果 NewsPublisher
和 Subscriber
在多线程环境下使用(例如新闻发布者和订阅者运行在不同的线程中),需要确保线程安全。
当前的实现是同步通知,即 notifySubscribers
会阻塞直到所有观察者处理完新闻。如果观察者的处理逻辑较慢,可能会影响性能,可使用异步机制(如线程池或消息队列)来通知观察者。
某些场景下,可能需要为观察者设置优先级(例如 VIP 订阅者优先接收新闻)。可在 Subscriber
中增加优先级字段,并在通知时按优先级排序。
当前的实现中,观察者需要直接依赖被观察者。如果希望进一步解耦,可以使用事件总线(Event Bus)或消息队列(Message Queue)作为中间层,引入事件总线,被观察者发布事件,观察者订阅事件。