什么是观察者模式
观察者模式(Observer Pattern)是一种行为型(Behavioral Pattern)设计模式,它定义了对象之间的一种一对多的依赖关系,使得当一个对象改变状态时,所有依赖于它的对象都会得到通知并自动更新。这种模式通常用于实现分布式事件处理系统、消息发布/订阅(pub-sub)机制等。在观察者模式中,主要涉及到两个角色:
-
主题(Subject):即“被观察者”,它维护一组观察者,提供用于注册和删除观察者的接口,并且在其内部状态发生变化时负责通知所有注册的观察者。
-
观察者(Observer):提供一个更新接口,用于在主题的状态发生变化时得到通知。观察者通过这个接口得知主题的最新状态并进行相应的处理。
观察者模式的关键点在于主题和观察者之间的解耦。观察者不需要知道主题的具体实现细节,主题也不需要知道观察者的具体实现细节。这种解耦使得系统中的各个组件可以独立地改变和复用,同时保持了它们之间的一致性。
在实现观察者模式时,通常会有一个抽象的主题接口和一个抽象的观察者接口。具体的主题和观察者类则分别实现这些接口。当主题的状态发生变化时,它会调用观察者接口的更新方法,以通知所有注册的观察者。由此,一个简单的观察者模式的实现可能包含以下几个部分:
1. 抽象主题(Subject Interface):Subject接口,定义注册、删除和通知观察者的方法。
2. 具体主题(Concrete Subject):实现Subject接口,实现状态变化时通知观察者的逻辑。
3. 抽象观察者(Observer Interface):Observer接口,定义更新接口,以便在得到主题的更改通知时更新自己。
4. 具体观察者(Concrete Observer):实现Observer接口,根据主题的通知来更新自己的状态。
观察者模式的优缺点:
优点
- 松耦合:观察者模式使得主题和观察者之间的关系是松散的,它们彼此之间并不直接依赖,这样就可以更容易地对它们进行修改和扩展。\
- 支持广播通知:主题可以通知多个观察者,从而实现一对多的通知机制。\
- 可以轻松添加新的观察者:由于观察者模式使用了抽象的观察者接口,因此可以很容易地添加新的观察者,而无需修改主题的代码。
- 支持开放-封闭原则:可以在不修改主题或观察者的情况下,增加新的主题或观察者。
缺点
- 如果观察者之间有循环依赖,可能会导致系统的复杂性增加。
- 观察者模式可能会导致性能问题,特别是当观察者数量庞大时,每次通知都需要遍历所有观察者。
- 当一个观察者与主题的交互比较复杂时,可能会导致观察者之间的调用顺序难以理解。
如何使用观察者模式
现在我们需要观测天气变化,如果下雨了,就要马上收衣服,如果不考虑任何设计模式,我们的代码大概是这样的:
static class Weather{
//是否下雨
public static boolean isRaining = false;
}
static class Person{
public void monitor(){
while (!Weather.isRaining){
//持续监控天气
}
//天气状态变了,下雨收衣服
}
}
在这段代码中,人需要持续地主动监控天气状态,有一个人需要在下雨时收衣服,就要有一个持续观测的while循环,有一百个人需要在下雨时收衣服,就要有一百个持续观测的while循环,显然,这会耗费大量的cpu资源,现在我们了解的观察者模式,就像是天气通过下雨的方式主动通知人们“下雨了”这一状态变化,而不再需要每个人持续不断地盯着天气是否下雨;代码即可优化为:
interface Observable{
List<Observer> observers = new ArrayList<>();
default void notifyObservers(){
observers.forEach(Observer::update);
}
default Observable addObserver(Observer observer){
this.observers.add(observer);
return this;
}
}
interface Observer{
void update();
}
static class Weather implements Observable{
//是否下雨
public boolean isRaining = false;
public void changeRainState(){
isRaining = !isRaining;
this.notifyObservers();
}
}
static class Person implements Observer{
@Override
public void update() {
//天气状态变了,下雨收衣服
}
}
在上述代码中,定义了两个接口以及两个实现类,分别对应着抽象主题Observable,具体主题Weather,抽象观察者Observer以及具体观察者Person,并使得天气在每次发生变化时主动通知观察者(notifyObservers),观察者只需要在触发了update时去收衣服即可,这样便实现了一个简单的观察者模式。
如何继续优化
如何根据不同情况做出不同反应:
在上面的示例代码中,天气变化了,就通知观察者收衣服,那天气由下雨变化为雨停了,此时观察者再去收衣服显然是不正确了,也就是说,此时观察者只知道天气变化了,而不知道是下雨了还是雨停了,也不知道雨下得有多大,无法根据不同的情况做出不同的反应,为了解决这个问题,我们可以将天气状态的所有变化情况封装到一个对象中,在通知观察者时,将对象作为参数一并传给观察者:
static class Event{
//下雨状态
boolean isRaining;
//降雨量
int rainfall;
public Event(boolean isRaining, int rainfall) {
this.isRaining = isRaining;
this.rainfall = rainfall;
}
}
interface Observable{
List<Observer> observers = new ArrayList<>();
default void notifyObservers(Event event){
observers.forEach(observer -> observer.update(event));
}
default Observable addObserver(Observer observer){
this.observers.add(observer);
return this;
}
}
interface Observer{
void update(Event event);
}
static class Weather implements Observable{
//是否下雨
public boolean isRaining = false;
public void changeRainState(){
isRaining = !isRaining;
this.notifyObservers(new Event(isRaining, 5));
}
}
static class Person implements Observer{
@Override
public void update(Event event) {
if(event.isRaining){
if(event.rainfall<3){
//雨下的很小,不用管
}else{
//雨下大了,收衣服
}
}else{
//雨停了,晾衣服
}
}
}
如何同时观察多个对象
现在,观察者Person可以根据天气的不同变化做出不同的反应了,但是还是存在一些问题的,Person作为一个观察者,要观察的事情可不仅仅是天气的变化这么简单的,比如Person在观察天气变化的同时,还要观察电饭煲里的米饭有没有熟,那么update方法仅接受一个天气变化情况的Event对象显然是不足以满足Person要观察多个对象的需求的,此时我们可以直接将被观察者作为参数传给观察者,观察者即可根据事件来源和其中的变量做出不同的反应:
interface Observable{
List<Observer> observers = new ArrayList<>();
default void notifyObservers(Observable source){
observers.forEach(observer -> observer.update(source));
}
default Observable addObserver(Observer observer){
this.observers.add(observer);
return this;
}
}
interface Observer{
void update(Observable source);
}
/**
* 被观察者--天气
*/
static class Weather implements Observable{
//是否下雨
public boolean isRaining = false;
public void changeRainState(){
isRaining = !isRaining;
this.notifyObservers(this);
}
}
/**
* 被观察者--电饭煲
*/
static class RiceCooker implements Observable{
public boolean isCooked = false;
public void changeCookedState(){
isCooked = true;
this.notifyObservers(this);
}
}
static class Person implements Observer{
@Override
public void update(Observable source) {
if(source instanceof Weather){
//天气变了
}else if(source instanceof RiceCooker){
//米饭熟了
}
}
}
JAVA自带的观察者接口
其实Java本身是已经定义好了观察者接口和被观察者接口的,分别是java.util.Observer和java.util.Observable,以下是它们的源码,在前面了解了观察者模式的原理和简单的实现之后,下面的源码就很好理解了:
总结
观察者模式是一种非常有用的设计模式,特别适用于需要实现发布-订阅机制的场景,它能够帮助我们实现对象之间的松耦合,提高系统的灵活性和可维护性。同时,在使用观察者模式时,需要注意避免潜在的循环依赖和性能问题。