1. 基本介绍
描述:
观察者模式又被称为发布订阅(Publish/Subscribe)模式,属于对象行为型模式。它定义对象间的一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象状态发生改变时,它的所有观察者都会收到通知并自动更新相关内容。主题是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。
使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁,即不希望这些对象是紧密耦合的。
应用实例:
- 一家报社(Subject)一发布报纸,就会立马派送给所有订报(Observer)的人,订报的人就能获取报纸内容。
- 用户界面可以作为一个观察者,业务数据是被观察者,用户界面观察业务数据的变化,发现数据变化后,就显示在界面上。
核心类说明:
- Subject:抽象主题,即被观察对象,本身维护一个观察者集合。
- Observer:抽象观察者,根据主题状态变化做出相应反应,本身维护一个主题的引用。
注意事项:
- Java 中已经有了对观察者模式的支持类。
- 避免循环引用。
- 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
2. 自己实现观察者模式
2.1 设计类图
自己设计观察者模式类图说明:
- Subject 接口:定义主题接口,对象使用此接口注册为观察者,或者把自己从观察者中删除,所有实现了此接口的实体类即为具体的主题类。
- Observer 接口:定义观察者接口,所有潜在的观察者必须实现观察者接口,这个接口只有 update() 一 个方法,当主题状态改变时它被调用。
- newspaperOffice 实体类:报社类,实现了主题接口,为被观察目标对象。具体的报社类除了实现主题接口中的方法,还提供了获取新闻信息和设置新闻信息方法。报社类设置保存有其观察者列表,为了简单起见使用字符串类型存储新闻信息。
- Eric 实体类:用户 Eric,实现了 Observer 接口,为观察者对象,并保存有对主题对象的引用。
- Shealtiel 实体类:用户 Shealtiel,实现了 Observer 接口,为观察者对象,并保存有对主题对象的引用。
- 其他实现 Subject 接口的具体的被观察对象类,其他实现了 Observer 接口的观察者类。
2.2 实现代码
(1)Subject主题接口
public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObserver();
}
(2)Observer观察者接口
public interface Observer {
void update(String news);
}
(3)NewspaperOffice报社类,实现Subject接口,为具体的被观察对象。
public class NewspaperOffice implements Subject {
private List<Observer> observers; // 报社的观察者列表
private String news = ""; // 报社新闻内容
public NewspaperOffice() {
observers = new ArrayList<>(); // 初始化观察者列表为空表
}
@Override
public void registerObserver(Observer o) {
if (o == null) {
throw new NullPointerException();
}
if (!observers.contains(o)) {
observers.add(o);
}
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObserver() {
for(Observer observer : observers){
observer.update(news); //更新观察者的信息
}
}
public void setNews(String news) {
this.news = news;
notifyObserver(); //设置新信息后通知观察者
}
}
(4)Eric用户类,实现Observer接口,订阅报社报纸,为观察者。
public class Eric implements Observer {
private Subject subject; //对观察的主题的引用
private String news;
public Eric(Subject subject){
this.subject = subject;
this.subject.registerObserver(this); //把此对象注册为具体主题的观察者
}
@Override
public void update(String news) {
this.news = news;
showNews(); //更新信息后显示最新信息
}
public void showNews(){
System.out.println("Eric: "+news);
}
public void unsubscribe(){
subject.removeObserver(this);
}
}
(5)Shealtiel用户类,实现Observer接口,订阅报社报纸,为观察者。
public class Shealtiel implements Observer {
private Subject subject; //对观察的主题的引用
private String news;
public Shealtiel(Subject subject){
this.subject = subject;
this.subject.registerObserver(this); //把此对象注册为具体主题的观察者
}
@Override
public void update(String news) {
this.news = news;
showNews(); //更新信息后显示最新信息
}
public void showNews(){
System.out.println("Shealtiel: "+news);
}
public void unsubscribe(){
subject.removeObserver(this);
}
}
(6)测试类
public class Demo {
public static void main(String[] args) {
//实例化报社类(被观察者),报社类实现了Subject接口,即为具体的主题类
NewspaperOffice newspaperOffice = new NewspaperOffice();
//实例化用户,并订阅报纸信息,即初始化为报社主题的观察者
Eric eric = new Eric(newspaperOffice);
Shealtiel shealtiel = new Shealtiel(newspaperOffice);
//报社更新信息,系统会自动通知相应的观察者
System.out.println("报社更新第一条信息,注意观察者接受的信息:");
newspaperOffice.setNews("This is the first news!");
System.out.println("\n报社更新第二条信息,注意观察者接受的信息:");
newspaperOffice.setNews("This is the second news!");
//eric对象取消订阅报纸新闻,则此后的新闻信息将不会通知eric,因为它不再是报社的观察者
System.out.println("\nEric取消订阅新闻信息");
eric.unsubscribe();
System.out.println("报社更新第三条信息,注意此时观察者数量:");
newspaperOffice.setNews("This is the third news!");
}
}
(7)测试结果
自己设计观察者模式测试结果
3. Java内置的观察者模式
1. 设计类图
Java内置的观察者设计模式类图说明:
- Observable类:Java内置可观察类,所有继承扩展此类的实体类为主题类。
- Observer接口:Java内置观察者接口,所有观察者必须实现观察者接口,这个接口只有update()一 个方法,当主题状态改变时它被调用。
注意一个为类,一个为接口。
Java内置的观察者模式运作方式,和我们自己实现的代码类似,但有一些小差异。最明显的差异是NewspaperOffice(也就是我们的主题)现在扩展自Observable类,并继承到一些增 加、删除、通知观察者的方法(以及其他的方法)。
- 如何把对象变成观察者?
如同以前一样,实现观察者接口(java.uitl.Observer),然后调用任何Observable主题对象的 addObserver() 方法。不想再当观察者时,调用 deleteObserver() 方法就可以了。
- 被观察者要如何送出通知?
首先,需要利用继承扩展java.util.Observable类产生具体的“被观察者”主题类,然后,需要两 个步骤:
- 先调用setChanged()方法,标记状态已经改变的事实;
- 然后调用两种 notifyObservers() 方法中的一个:
notifyObservers() 或 notifyObservers(Object arg)
- 观察者如何接收通知?
同以前一样,观察者实现了更新的方法,但是方法的签名不太一样:
update(Observable o, Object arg)
如果你想“推”(push)数据给观察者,你可以把数据当作数据对象传送给 notifyObservers(arg)方法。否则,观察者就必须从可观察者对象中“拉”(pull)数据。
- 了解一下setChanged()方法的具体作用。
//伪代码:
setChanged() {
changed = true //把改变状态设置为true
}
//只有在changed值为true时通知观察者
notifyObservers(Object arg) {
if (changed) {
for every observer on the list {
call update (this, arg)
}
changed = false
}
}
notifyObservers() {
notifyObservers(null)
}
2. 使用Java内置观察者模式实现
(1)NewspaperOffice报社类,继承扩展Java内置Observable类,为被观察对象。
public class NewspaperOffice extends Observable {
private String news; // 报社新闻内容
//注意与自己实现的不同处,添加观察者放在超类中处理
public NewspaperOffice() {}
public void setNews(String news) {
this.news = news;
newsChanged();
}
public String getNews() {
return news;
}
// 内容改变时,通知观察者
public void newsChanged() {
setChanged();
notifyObservers();
}
}
(2)Eric用户类,实现Java内置Observer接口,订阅报社报纸,为观察者。
public class Eric implements Observer {
private Observable observable; // 主题的引用
private String news;
public Eric(Observable observable){
this.observable = observable;
this.observable.addObserver(this); // 把此对象添加为观察者
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof NewspaperOffice) {
NewspaperOffice newspaperOffice = (NewspaperOffice) o;
this.news = newspaperOffice.getNews();
showNews(); //更新信息后显示信息
}
}
public void showNews(){
System.out.println("Eric: "+news);
}
}
(3)Shealtiel用户类,实现Java内置Observer接口,订阅报社报纸,为观察者。
public class Shealtiel implements Observer {
private Observable observable; //主题的引用
private String news;
public Shealtiel(Observable observable){
this.observable = observable;
this.observable.addObserver(this); //把此对象添加为观察者
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof NewspaperOffice) {
NewspaperOffice newspaperOffice = (NewspaperOffice) o;
this.news = newspaperOffice.getNews();
showNews(); //更新信息后显示信息
}
}
public void showNews(){
System.out.println("Shealtiel: "+news);
}
}
(4)测试类
public class Demo {
public static void main(String[] args) {
//实例化报社类(被观察者)
NewspaperOffice newspaperOffice = new NewspaperOffice();
//实例化订阅者类(观察者)
Eric eric = new Eric(newspaperOffice);
Shealtiel shealtiel = new Shealtiel(newspaperOffice);
//报社更新信息,自动会通知相应的观察者
System.out.println("报社更新第一条信息,注意观察者接受的信息:");
newspaperOffice.setNews("This is the first news!");
System.out.println("\n报社更新第二条信息,注意观察者接受的信息:");
newspaperOffice.setNews("This is the second news!");
//eric对象取消订阅报纸新闻,则此后的新闻信息将不会通知eric
System.out.println("\nEric取消订阅新闻信息");
newspaperOffice.deleteObserver(eric);
System.out.println("报社更新第三条信息,注意此时观察者数量:");
newspaperOffice.setNews("This is the third news!");
}
}
(5)测试结果
使用Java内置的观察者设计模式测试结果
3. 另一种实现
报社类:
public class NewsOffice extends Observable {
// 报社设计为单例模式(饿汉式)
private static NewsOffice instance = new NewsOffice();
private NewsOffice() {}
public static NewsOffice getInstance() {
return instance;
}
// 新闻内容
private String news;
public void setNews(String news) {
this.news = news;
updateNews();
}
private void updateNews() {
System.out.println("报社更新了新闻...新闻内容:" + news);
// 设置改变通知观察者
this.setChanged();
this.notifyObservers(news);
}
}
两个用户类:
public class Eric implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("Eric观察对象为:" + o.getClass().getName());
System.out.println("收到得新闻:" + arg.toString());
}
}
public class Jack implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("Jack观察的对象为:" + o.getClass().getName());
System.out.println("收到的新闻:" + arg.toString());
}
}
测试类:
public class Test {
public static void main(String[] args) {
NewsOffice newsOffice = NewsOffice.getInstance();
Eric user1 = new Eric();
Jack user2 = new Jack();
newsOffice.addObserver(user1);
newsOffice.addObserver(user2);
newsOffice.setNews("还有两天就中秋节了!!!");
}
}
4. 另一个实例电商观察产品列表的实例
- 首先定义被观察者类,需要继承
Observable
类,在相应发生变化的方法中调用父类的setChanged()
和notifyObservers()
方法,以用于通知相应的观察者,并使它们自动做出变化更新。 - 其次需要定义观察者,需要实现
Observer
接口,并实现update()
方法,用于在被观察对象发生变化并调用notifyObservers()
方法后自动执行观察者的更新逻辑。 - 测试时,需要首先创建被观察者和观察者的实例对象,然后被观察者对象调用 Observable 类中的
addObserver()
方法添加相应的实例对象作为它的观察者。之后被观察对象发生的更新变化都会自动通知并自动更新到观察者中。
产品列表类(被观察对象):
public class ProductList extends Observable {
// 使用饿汉式的单例模式
private static ProductList instance = new ProductList();
private ProductList() {}
public static ProductList getInstance() {
return instance;
}
private List<String> productList = new ArrayList<>(); // 产品列表
//向产品列表中增加新产品,并会通知相应的观察者
public void addProduct(String newProduct) {
this.productList.add(newProduct);
System.out.println("产品列表中增加了新的产品,名称为:" + newProduct);
// 调用父类的方法,设置被观察者的状态发生了变化
this.setChanged();
// 调用父类的方法,通知所有观察者发生的变化,并传递发生变化的对象
this.notifyObservers(newProduct);
}
}
京东电商类(观察者):
public class JingDongObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("京东电商观察的对象为:" + o.getClass().getName());
String newProduct = (String) arg;
System.out.println(newProduct + " 已同步到京东电商");
}
}
淘宝电商类(观察者):
public class TaoBaoObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("淘宝电商观察的对象为:" + o.getClass().getName());
String newProduct = (String) arg;
System.out.println(newProduct + " 已同步到淘宝电商");
}
}
测试:
public class ObserverTest {
public static void main(String[] args) {
// 创建产品列表实例,作为被观察对象
ProductList productList = ProductList.getInstance();
// 创建观察产品列表的电商实例,作为观察者
JingDongObserver jdObserver = new JingDongObserver();
TaoBaoObserver taoBaoObserver = new TaoBaoObserver();
// 产品列表添加上述两个电商对象作为观察者
productList.addObserver(jdObserver);
productList.addObserver(taoBaoObserver);
// 使用 addProduct 方法向产品列表中添加新产品
// 会在 addProduct 方法中调用相应父类方法通知观察者此变化,并会自动执行观察者中的 update 方法
productList.addProduct("产品一号");
}
}
5. 总结
5.1 优点
- 观察者和被观察者是抽象耦合的,让耦合的双方都依赖于抽象,而不是依赖具体。
- 对对象解耦,将观察者和被观察者完全隔离。
- 使用对象间的组合关系,更加灵活。
5.2 缺点
- 如果一个被观察者对象有很多的直接和间接的观察者,通知所有的观察者会花费很多时间。
- 如果在观察者和所观察主题之间有循环依赖的话,观察主题会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式仅仅让观察者知道所观察的主题对象发生了变化,但没有相应的机制让观察者知道所观察的目标主题对象到底是如何发生变化的。
5.3 自己实现观察者模式思路
首先设计两个接口,一个为主题接口Subject,另一个为观察者接口Observer。
然后具体的主题类实现Subject接口,具体的观察者类实现Observer接口。
当某一观察者对象注册为某一主题对象的观察者后,就可以接收此主题对象发布的通知。
5.4 使用Java内置观察者模式思路
不用自己设计接口,使用Java内置的可观察类Observable和观察者接口Observer。
具体的可观察类(主题类)继承扩展Observable类,具体的观察者类实现Observer接口。
当某一观察者对象注册为某一主题对象的观察者后,就可以接收此主题对象发布的通知。
注:使用接口能够统一标准,且更加灵活;多用组合,少用继承。
写在最后
大家好,我是葛数,一个热爱生活的程序员,如果这篇文章对您有所帮助,还请大家给点个赞哦👍。
另外,欢迎大家关注本人公众号:CoderGeshu,一个致力于分享编程技术知识的公众号,定有您所要 ~~
网友评论