设计模式 ~ 行为型模式 ~ 观察者模式 ~ Observer Pattern。
概述。
定义。
又被称为发布-订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
结构。
在观察者模式中有如下角色。
-
Subject:抽象主题(抽象被观察者)。
抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。 -
ConcreteSubject:具体主题(具体被观察者)。
该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。 -
Observer:抽象观察者。
是观察者的抽象类,ta 定义了一个更新接口,使得在得到主题更改通知时更新自己。 -
ConcrereObserver:具体观察者。
实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
案例实现。
【eg.】微信公众号。
在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。
类图如下。
代码如下。
定义抽象观察者类,里面定义一个更新的方法。
package com.geek.observer.pattern;
/**
* @author geek
*/
public interface IObserver {
/**
* 更新。
*
* @param message
*/
void update(String message);
}
定义具体观察者类,微信用户是观察者,里面实现了更新的方法。
package com.geek.observer.pattern;
/**
* 具体观察者角色。
*
* @author geek
*/
public class WeiXinUser implements IObserver {
/**
* 微信用户名。
*/
private final String name;
public WeiXinUser(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(this.name + " ~ " + message);
}
}
定义抽象主题类,提供了 attach、detach、notify 三个方法。
package com.geek.observer.pattern;
/**
* 抽象主题。
*
* @author geek
*/
public interface ISubject {
/**
* 增加订阅者/观察者对象。
*
* @param observer
*/
void attach(IObserver observer);
/**
* 删除订阅者。
*
* @param observer
*/
void detach(IObserver observer);
/**
* 通知订阅者更新消息。
*
* @param message
*/
void notify(String message);
}
微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法。
package com.geek.observer.pattern;
import java.util.ArrayList;
import java.util.List;
/**
* 具体主题。具体被观察者。
*
* @author geek
*/
public class SubscriptionSubject implements ISubject {
/**
* 储存订阅公众号的微信用户(观察者对象)。
*/
private final List<IObserver> weiXinUserList = new ArrayList<IObserver>();
/**
* 增加订阅者。
*
* @param observer
*/
@Override
public void attach(IObserver observer) {
this.weiXinUserList.add(observer);
}
/**
* 删除订阅者。
*
* @param observer
*/
@Override
public void detach(IObserver observer) {
this.weiXinUserList.remove(observer);
}
/**
* 通知订阅者更新消息。
*
* @param message
*/
@Override
public void notify(String message) {
for (IObserver observer : this.weiXinUserList) {
observer.update(message);
}
}
}
客户端程序。
package com.geek.observer.pattern;
/**
* @author geek
*/
public class Client {
public static void main(String[] args) {
// 创建公众号对象。
SubscriptionSubject subscriptionSubject = new SubscriptionSubject();
// 创建微信用户。
WeiXinUser weiXinUser1 = new WeiXinUser("孙悟空");
WeiXinUser weiXinUser2 = new WeiXinUser("猪悟能");
WeiXinUser weiXinUser3 = new WeiXinUser("沙悟净");
// 订阅公众号。
subscriptionSubject.attach(weiXinUser1);
subscriptionSubject.attach(weiXinUser2);
subscriptionSubject.attach(weiXinUser3);
// 公众号更新发出消息给订阅的微信用户(观察者对象)。
subscriptionSubject.notify("Geek 的专栏更新了。");
}
}
/*
Connected to the target VM, address: '127.0.0.1:59938', transport: 'socket'
孙悟空 ~ Geek 的专栏更新了。
猪悟能 ~ Geek 的专栏更新了。
沙悟净 ~ Geek 的专栏更新了。
Disconnected from the target VM, address: '127.0.0.1:59938', transport: 'socket'
Process finished with exit code 0
*/
优缺点。
优点。
-
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
-
被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】。
缺点。
-
如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
-
如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃
使用场景。
-
对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
-
当一个抽象模型有两个方面,其中一个方面依赖于另一方面时。
JDK 中提供的实现。
在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
1,Observable 类。
Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
-
void addObserver(Observer o); 方法:用于将新的观察者对象添加到集合中。
-
void notifyObservers(Object arg); 方法:调用集合中的所有观察者对象的 update(); 方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
-
void setChange(); 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为 true 时,notifyObservers(); 才会通知观察者。
2,Observer 接口。
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update(); 方法,进行相应的工作。
【eg.】警察抓小偷。
警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。代码如下。
小偷是一个被观察者,所以需要继承 Observable 类。
package com.geek.observer.pattern.demo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Observable;
/**
* @author geek
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Thief extends Observable {
private String name;
public void steal() {
System.out.println("小偷:我偷东西了,有没有人来抓我!!!");
// changed = true。
super.setChanged();
super.notifyObservers();
}
}
警察是一个观察者,所以需要让其实现 Observer 接口。
package com.geek.observer.pattern.demo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Observable;
import java.util.Observer;
/**
* @author geek
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Policemen implements Observer {
private String name;
/**
* This method is called whenever the observed object is changed. An
* application calls an <tt>Observable</tt> object's
* <code>notifyObservers</code> method to have all the object's
* observers notified of the change.
*
* @param o the observable object.
* @param arg an argument passed to the <code>notifyObservers</code>
* method.
*/
@Override
public void update(Observable o, Object arg) {
System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!");
}
}
客户端代码。
package com.geek.observer.pattern.demo;
/**
* @author geek
*/
public class Client {
public static void main(String[] args) {
// 创建小偷对象。
Thief thief = new Thief("隔壁老王");
// 创建警察对象。
Policemen policemen = new Policemen("小李");
// 让警察盯着小偷。
thief.addObserver(policemen);
// 小偷偷东西。
thief.steal();
}
}