观察者模式(Observer Pattern)是一种行为设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
一、核心思想
观察者模式的核心思想是解耦主题(被观察者)和观察者,使得它们可以独立变化而不会相互影响。这有助于降低对象之间的耦合度,提高系统的可扩展性和可维护性。
二、定义与结构
1. 定义
观察者模式定义了对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
2. 结构
- Subject(主题):也称为被观察者,它是一个接口或者抽象类,定义了添加、删除和通知观察者的方法。它维护了一个观察者列表,当自身状态发生变化时,通过遍历这个列表来通知所有的观察者。
- ConcreteSubject(具体主题):实现了Subject接口,它包含了一些可以被观察的状态属性,并且在这些状态发生变化时,负责调用通知方法来通知观察者。
- Observer(观察者):也是一个接口或者抽象类,它定义了一个更新方法,用于在收到主题通知时更新自己的状态。
- ConcreteObserver(具体观察者):实现了Observer接口,它包含了用于存储自身状态的属性,并且实现了更新方法,在更新方法中根据主题传递过来的信息更新自己的状态。
三、角色
1. 主题(Subject)
- 职责是管理观察者对象的引用列表,提供添加和删除观察者的方法。
- 当自身状态发生变化时,负责通知所有已注册的观察者。
2. 观察者(Observer)
- 定义了一个更新接口,当收到主题的通知时,会调用这个更新接口来更新自己的状态。
四、实现步骤及代码示例
1. 定义观察者接口
java
// 观察者接口
public interface Observer {
void update(String news);
}
2. 定义主题接口
java
import java.util.ArrayList;
import java.util.List;
// 主题接口
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers(String news);
}
3. 实现具体主题类
java
import java.util.ArrayList;
import java.util.List;
// 具体主题类
public class NewsPublisher implements Subject {
private List<Observer> observers = new ArrayList<>();
private String latestNews;
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(String news) {
this.latestNews = news;
for (Observer observer : observers) {
observer.update(latestNews);
}
}
}
4. 实现具体观察者类
java
// 具体观察者类
public class NewsSubscriber implements Observer {
private String name;
public NewsSubscriber(String name) {
this.name = name;
}
@Override
public void update(String news) {
System.out.println(name + " received news: " + news);
}
}
5. 测试代码
java
public class Main {
public static void main(String[] args) {
NewsPublisher publisher = new NewsPublisher();
NewsSubscriber subscriber1 = new NewsSubscriber("Subscriber 1");
NewsSubscriber subscriber2 = new NewsSubscriber("Subscriber 2");
publisher.attach(subscriber1);
publisher.attach(subscriber2);
publisher.notifyObservers("New technology released!");
}
}
五、常见技术框架应用
1、Vue 2 数据劫持
在Vue 2中,观察者模式是通过Object.defineProperty()
方法实现的,这允许Vue能够追踪数据对象属性的变化,并在变化发生时触发相应的更新。以下是对Vue 2中基于Object.defineProperty()
实现的观察者模式的详细解析:
1.1. 数据劫持
Vue 2 使用Object.defineProperty()
来对数据对象的属性进行劫持。这个方法允许我们定义一个对象属性的getter和setter函数,当属性被访问或修改时,这些函数会被调用。
javascript
Object.defineProperty(obj, 'property', {
get: function() {
// 当属性被访问时执行的代码
},
set: function(newValue) {
// 当属性被修改时执行的代码
}
});
在Vue中,当数据对象被创建时,Vue会使用Object.defineProperty()
遍历对象的所有属性,并为它们设置getter和setter。这样,当这些属性在将来被访问或修改时,Vue就能够感知到。
1.2. 依赖收集
在getter函数中,Vue会进行依赖收集。所谓依赖收集,就是当某个属性被访问时,Vue会记录下当前正在执行的Watcher(观察者)。Watcher是Vue内部用于追踪数据变化的机制,它会在数据变化时触发相应的更新。
当组件渲染或计算属性被计算时,它们会访问数据对象的属性,这时getter函数就会被调用,Vue就会记录下当前的Watcher。
1.3. 派发更新
在setter函数中,当数据属性被修改时,Vue会触发setter函数。在这个函数中,Vue会遍历之前收集的依赖(Watcher),并通知它们数据已经发生了变化,需要重新执行以更新视图或计算属性。
1.4. 观察者(Watcher)
Watcher是Vue内部的一个类,它用于追踪数据的变化并在数据变化时执行相应的回调函数。在Vue中,Watcher有三种类型:
- 渲染Watcher:与组件的渲染函数相关联,当数据变化时,它会重新渲染组件。
- 计算属性Watcher:与计算属性相关联,当计算属性的依赖数据变化时,它会重新计算计算属性的值。
- 侦听器Watcher :通过
vm.$watch
方法创建的Watcher,用于在数据变化时执行特定的回调函数。
1.5. 实现细节
Vue 2的内部实现非常复杂,但基于上述原理,我们可以简化地理解其观察者模式的实现过程:
- 初始化数据 :使用
Object.defineProperty()
为数据对象的每个属性设置getter和setter。 - 依赖收集:在getter函数中,检查当前是否存在活动的Watcher(通常是在组件渲染或计算属性计算时),如果存在,则将其添加到该属性的依赖列表中。
- 派发更新:在setter函数中,当属性值发生变化时,遍历该属性的依赖列表,并通知每个依赖(Watcher)数据已经变化,需要执行更新操作。
1.6. 注意事项
- 由于
Object.defineProperty()
只能对对象的已有属性进行劫持,因此Vue无法检测到对象属性的添加或删除。为了解决这个问题,Vue提供了Vue.set()
和Vue.delete()
方法。 - 深度监听:默认情况下,Vue只监听对象的第一层属性的变化。如果需要监听嵌套对象的变化,可以在创建Vue实例时通过
observe
选项开启深度监听(但请注意性能开销)。然而,在Vue 2中,深度监听通常是通过递归地为嵌套对象设置getter和setter来实现的,而不是简单地通过observe
选项。实际上,observe
选项在Vue 2中并不直接控制深度监听。 - 响应式系统的局限性:由于JavaScript的限制和性能考虑,Vue的响应式系统有一些局限性。例如,它不能检测数组元素通过索引的直接修改或对象属性的添加/删除(除非使用
Vue.set()
/Vue.delete()
)。
综上所述,Vue 2通过Object.defineProperty()
实现了观察者模式,使得Vue能够追踪数据对象属性的变化并在变化发生时自动更新视图。然而,由于JavaScript的限制和性能考虑,Vue的响应式系统有一些局限性需要注意。
2. JavaScript 中 事件监听
javascript
// 创建一个按钮元素
const button = document.createElement('button');
button.textContent = 'Click me';
// 定义观察者函数(事件处理函数)
const observerFunction = function () {
console.log('Button was clicked!');
};
// 主题(按钮)添加观察者(事件监听)
button.addEventListener('click', observerFunction);
// 将按钮添加到文档中
document.body.appendChild(button);
在这个JavaScript示例中,button
是主题,observerFunction
是观察者。当按钮被点击(主题状态改变)时,会触发观察者函数,这类似于观察者模式的通知机制。
六、应用场景
javascript
当一个对象的改变需要同时改变其他对象,而不知道具体有多少个对象有待改变时。
当一个抽象模型有两个方面,其中一个方面依赖于另一方面,这时可以通过观察者模式将这两者封装在独立的对象中以使它们各自独立地改变和复用。
当对一个对象的改变需要广播到其他对象时。
观察者模式广泛应用于各种需要通知多个对象进行同步更新的场合,包括但不限于:
- GUI事件监听机制:在图形用户界面编程中,按钮、文本框等控件的事件处理通常使用观察者模式。
- 数据模型与视图同步:在MVC架构中,观察者模式常用于数据模型和视图之间的更新同步。
- 发布-订阅系统:观察者模式是发布-订阅系统的基础,允许不同的服务订阅某个主题并接收通知。
- 股票价格监控:在金融系统中,观察者模式可以让股票价格的变化自动通知所有依赖该数据的系统。
- 社交媒体的通知机制:当用户发布新动态时,所有关注者都会收到通知。
七、优缺点
优点
- 解耦性高:主题和观察者之间是松耦合的关系。主题只需要知道观察者实现了更新方法,而不需要了解观察者的具体细节。这样可以方便地添加、删除和替换观察者,同时也使得主题和观察者可以独立地进行修改和扩展。
- 支持广播通信:主题可以同时通知多个观察者,使得系统能够方便地实现一对多的消息传递机制,提高了信息传播的效率。
缺点
- 可能导致性能问题:如果观察者数量过多,当主题状态发生变化时,通知所有观察者可能会消耗较多的时间和资源。特别是在一些对性能要求较高的场景下,可能需要对通知机制进行优化,比如采用异步通知等方式。
- 存在循环依赖风险:如果观察者在更新自己状态的过程中又对主题进行了操作,可能会导致循环依赖,使得系统的逻辑变得复杂,甚至出现死循环等问题。在设计和实现过程中需要特别注意避免这种情况。