前言
今天我们来看下一个可以有效实现松耦合的设计模式——观察者模式,这个设计模式我之前也仅仅停留在听说过的层面,关于它的具体实现更是一知半解,所以今天我们就来简单剖析下这个设计模式。
设计模式
观察者模式
观察者模式定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
要点
- 观察者模式定义了对象之间一对多的关系
- 主题(可观察者)用一个共同的接口来更新观察者
- 观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口
- 使用次模式时,你可以从被观察者处推或拉数据(推的方式被认为是更正确的)
- 有多个观察者时,不可以依赖特定的通知次序
java
中有多种观察者模式的实现,包括了通用的java.util.Observable
,不过需要注意Observable
实现上所带来的问题,有必要的话,可以实现自己的Observable
Swing
大量使用观察者模式,许多GUI
框架也是如此- 观察者模式还广泛被应用在许多地方,比如:
JavaBeans
、RMI
示例
下面我们以一个具体实例,来展示下观察者模式的具体实现。这里我们就直接以《Head First
设计模式》中的气象站为例,其中天气信息就表示被观察者,天气布告板就表示订阅者和观察者,当天气发生变化(被观察者)时,会通过notifyObserver
通知所有观察者,并调用他们的控制方法处理数据。下面我们就来看下具体实现过程
被观察者接口接口
这里有三个方法,第一个是注册观察者,第二个是移除观察者,第三个是通知观察者
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObserver();
}
观察者接口
观察者接口就一个方法,主要用于更新从被观察者中获取到的数据,该方法会在被注册者的notifyObserver
方法中被调用
public interface Observer {
void update(float temp, float humidity, float pressure);
}
控制层接口
这个接口主要是用于处理被观察者更新的数据,核心方法就一个。
public interface DisplayElement {
void display();
}
被观察者实现
这里是被观察者的具体实现,被观察者也可以叫数据源,当数据发生变化时,由它负责告知观察者。
public class WeatherData implements Subject {
private List<Observer> observerList;
private float temp;
private float humidity;
private float pressure;
public WeatherData(List<Observer> observerList) {
this.observerList = observerList;
}
@Override
public void registerObserver(Observer observer) {
observerList.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observerList.remove(observer);
}
@Override
public void notifyObserver() {
observerList.forEach(observer -> observer.update(temp, humidity, pressure));
}
public void measurementChanged() {
notifyObserver();
}
public void setMeasurements(float temp, float humidity, float pressure) {
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
measurementChanged();
}
}
观察者实现
观察者我们实现了两个,主要是便于后面测试。这两个实现处理名字不一样,其他代码都是一样的。
public class RemoteDisplay implements Observer, DisplayElement {
private Subject subject;
private float temp;
private float humidity;
private float pressure;
public RemoteDisplay(Subject subject) {
this.subject = subject;
subject.registerObserver(this);
}
@Override
public void display() {
System.out.println(String.format("=======
name:%s
temp:%S
humidity:%s
pressure:%s", "remote", temp, humidity, pressure));
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
display();
}
}
public class CurrentWeatherDataDisplay implements Observer, DisplayElement {
private Subject subject;
private float temp;
private float humidity;
private float pressure;
public CurrentWeatherDataDisplay(Subject subject) {
this.subject = subject;
subject.registerObserver(this);
}
@Override
public void display() {
System.out.println(String.format("=======
name:%s
temp:%S
humidity:%s
pressure:%s", "urrentWeatherData", temp, humidity, pressure));
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
display();
}
}
测试代码
从上面观察者的实现类中,我们可以看出来,在实例化观察者的时候,其实已经完成了注册操作,所以这里我们不再需要手动注册观察者,后面再被观察者(数据)发生变化时,观察者会实时被通知。
@Test
public void testWeatherDisplay() {
WeatherData data = new WeatherData(new ArrayList<>());
new CurrentWeatherDataDisplay(data);
new RemoteDisplay(data);
data.setMeasurements(12, 88, 981);
System.out.println("----------------分割线------------------");
data.setMeasurements(28, 68, 871);
System.out.println("----------------分割线------------------");
data.setMeasurements(38, 48, 681);
}
运作结果
总结
从实例的运行结果来看,观察者模式特别适用于那些一对多的应用场景,比如数据推送同步,当你的平台数量发生变化时,数据发送方只需要将相关平台注册或删除,即可完成观察者的变更,可以实现代码之间的松耦合,提升代码的扩展性。
与这个设计模式类似的设计模式是订阅者模式,这两种设计模式,在我之前的认知中,我觉得他们应该是一样的,但是今天查阅了相关资料之后,发现它们并不完全一样,这里我们先大概对订阅者模式有一个基本的认知,后面等我们分享完订阅者模式之后,我们再来比较这两个的区别。