在开发过程中,当遇到如下场景:
微信公众号
的消息通知模型,只要公众号更新了消息,那么所有订阅了该公众号的用户都会收到消息推送。拍卖
的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。就上述场景而言,一方更新消息,其余用户获取消息。
由于在公众号消息通知模型中,订阅消息的用户可能增加、减少,因此,需要一种设计模式来解决此场景下的问题。
观察者模式就是应对此场景的一种设计模式。这里更新消息的一方称为主题(公众号)
,所有接收消息的用户称为观察者(订阅公众号的用户)
。
设计方案如类图1
所示,Subject 是自定义主题接口,其中包含注册观察者的方法 registerObserver();移除观察者的方法removeObserver();消息更新后,通知所有观察者的方法notifyAllObservers()。
WeChatPublicAccount 类是微信公众号,实现了Subject 接口。维护了一个 observersList ,用于管理所有的观察者,除此,还包括文章的标题、作者和内容等属性。
Observer 接口,为所有的观察者提供了统一的接口,并具有update()方法,用于更新信息。
User1类、User2类和User3类,是订阅了微信公众号的观察者。
Subject 接口:
package headfirst.designpatterns.observer.simple;
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyAllObservers();
}
WeChatPublicAccount 类:
package headfirst.designpatterns.observer.simple;
import java.util.ArrayList;
public class WeChatPublicAccount implements Subject {
private ArrayList<Observer> observersList;
private String title;
private String author;
private String content;
public WeChatPublicAccount() {
observersList = new ArrayList<>();
}
public ArrayList<Observer> getObserversList() {
return observersList;
}
@Override
public void registerObserver(Observer observer) {
if (observer != null) observersList.add(observer);
}
@Override
public void removeObserver(Observer observer) {
int index = -1;
if (observer != null)
index = observersList.indexOf(observer);
if (index != -1)
observersList.remove(index);
}
// 数据由主题由 “推” 的方式通知观察者
@Override
public void notifyAllObservers() {
for (Observer o : observersList) {
if (o != null)
o.update(title, author, content);
}
}
public void dataChanged() {
notifyAllObservers();
}
public void setDataChanged(String title, String author, String content) {
this.title = title;
this.author = author;
this.content = content;
dataChanged();
}
}
Observer 接口:
package headfirst.designpatterns.observer.simple;
public interface Observer {
void update(String title,String author,String content);
}
User1 类:
package headfirst.designpatterns.observer.simple;
public class User1 implements Observer {
private String title;
private String author;
private String content;
private Subject weChatPublicAccount;
public User1(Subject weChatPublicAccount) {
this.weChatPublicAccount = weChatPublicAccount;
weChatPublicAccount.registerObserver(this);
}
@Override
public void update(String title, String author, String content) {
this.title = title;
this.author = author;
this.content = content;
}
@Override
public String toString() {
return "User1{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
", content='" + content + '\'' +
'}';
}
}
User2 类:
package headfirst.designpatterns.observer.simple;
public class User2 implements Observer {
private String title;
private String author;
private String content;
private Subject weChatPublicAccount;
public User2(Subject weChatPublicAccount) {
this.weChatPublicAccount = weChatPublicAccount;
weChatPublicAccount.registerObserver(this);
}
@Override
public void update(String title, String author, String content) {
this.title = title;
this.author = author;
this.content = content;
}
@Override
public String toString() {
return "User2{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
", content='" + content + '\'' +
'}';
}
}
User3:
package headfirst.designpatterns.observer.simple;
public class User3 implements Observer {
private String title;
private String author;
private String content;
private Subject weChatPublicAccount;
public User3(Subject weChatPublicAccount) {
this.weChatPublicAccount = weChatPublicAccount;
weChatPublicAccount.registerObserver(this);
}
@Override
public void update(String title, String author, String content) {
this.title = title;
this.author = author;
this.content = content;
}
@Override
public String toString() {
return "User3{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
", content='" + content + '\'' +
'}';
}
}
Test:
package headfirst.designpatterns.observer.simple;
public class Test {
public static void main(String[] args) {
WeChatPublicAccount weChatPublicAccount = new WeChatPublicAccount();
Observer user1 = new User1(weChatPublicAccount);
Observer user2 = new User2(weChatPublicAccount);
Observer user3 = new User3(weChatPublicAccount);
System.out.println(user1.toString());
System.out.println(user2.toString());
System.out.println(user3.toString());
// 更新数据
weChatPublicAccount.setDataChanged("午觉睡多久比较好","丁香医生","午觉睡10-20mins是极好的,一觉醒来元气满满");
System.out.println("丁香医生公众号数据更新后...");
System.out.println(user1.toString());
System.out.println(user2.toString());
System.out.println(user3.toString());
}
}
输出: 在微信公众号更新数据后,所有的观察者都将收到公众号推送的数据。
User1{title='null', author='null', content='null'}
User2{title='null', author='null', content='null'}
User3{title='null', author='null', content='null'}
丁香医生公众号数据更新后...
User1{title='午觉睡多久比较好', author='丁香医生', content='午觉睡10-20mins是极好的,一觉醒来元气满满'}
User2{title='午觉睡多久比较好', author='丁香医生', content='午觉睡10-20mins是极好的,一觉醒来元气满满'}
User3{title='午觉睡多久比较好', author='丁香医生', content='午觉睡10-20mins是极好的,一觉醒来元气满满'}
利用 JDK 自带的 Observable 类和 Observer 接口,设计的方案如类图2所示。Observable 类和 1.2节中的 Subject 接口功能类似,其维护了一个 obs 列表,用于管理所有观察者。
WeChatPublicAccount 类继承了 Observable 类,其中在公众号的数据更新后,可将所有数据封装至一个 HashMap dataMap中,然后以dataMap 参数,调用 notifyObservers(Object arg) 方法可逐一通知所有观察者,实现数据推送。
类图2.通过Observable接口(推)实现微信公众号的主题-订阅模型
WeChatPublicAccount 类:
package headfirst.designpatterns.observer.observablepush;
import java.util.HashMap;
import java.util.Observable;
public class WeChatPublicAccount extends Observable {
private String title;
private String author;
private String content;
private HashMap<String, String> dataMap;
public WeChatPublicAccount() {
dataMap = new HashMap<>();
}
// 将数据封装至 map,然后以 “推” 的方式将数据通知所有观察者。
public void dataChanged() {
// 当状态变化满足一定条件时,调用 setChanged() 标志状态已改变,可通知观察者。例如,温度变化1°,才允许标志状态已改变,可通知观察者。
setChanged();
dataMap.put("title", title);
dataMap.put("author", author);
dataMap.put("content", content);
notifyObservers(dataMap);
}
public void setDataChanged(String title, String author, String content) {
this.title = title;
this.author = author;
this.content = content;
dataChanged();
}
}
User1类:
package headfirst.designpatterns.observer.observablepush;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
public class User1 implements Observer {
private String title;
private String author;
private String content;
private Observable weChatPublicAccount;
public User1(Observable weChatPublicAccount) {
this.weChatPublicAccount = weChatPublicAccount;
weChatPublicAccount.addObserver(this);
}
@Override
public String toString() {
return "User1{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
", content='" + content + '\'' +
'}';
}
@Override
public void update(Observable o, Object arg) {
HashMap<String,String> dataMap = (HashMap<String, String>) arg;
title = dataMap.get("title");
author = dataMap.get("author");
content = dataMap.get("content");
}
}
User2 类:
package headfirst.designpatterns.observer.observablepush;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
public class User2 implements Observer {
private String title;
private String author;
private String content;
private Observable weChatPublicAccount;
public User2(Observable weChatPublicAccount) {
this.weChatPublicAccount = weChatPublicAccount;
weChatPublicAccount.addObserver(this);
}
@Override
public String toString() {
return "User2{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
", content='" + content + '\'' +
'}';
}
@Override
public void update(Observable o, Object arg) {
HashMap<String,String> dataMap = (HashMap<String, String>) arg;
title = dataMap.get("title");
author = dataMap.get("author");
content = dataMap.get("content");
}
}
User3类:
package headfirst.designpatterns.observer.observablepush;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
public class User3 implements Observer {
private String title;
private String author;
private String content;
private Observable weChatPublicAccount;
public User3(Observable weChatPublicAccount) {
this.weChatPublicAccount = weChatPublicAccount;
weChatPublicAccount.addObserver(this);
}
@Override
public String toString() {
return "User3{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
", content='" + content + '\'' +
'}';
}
@Override
public void update(Observable o, Object arg) {
HashMap<String,String> dataMap = (HashMap<String, String>) arg;
title = dataMap.get("title");
author = dataMap.get("author");
content = dataMap.get("content");
}
}
Test类:
package headfirst.designpatterns.observer.observablepush;
import java.util.Observer;
public class Test {
public static void main(String[] args) {
WeChatPublicAccount weChatPublicAccount = new WeChatPublicAccount();
Observer user1 = new User1(weChatPublicAccount);
Observer user2 = new User2(weChatPublicAccount);
Observer user3 = new User3(weChatPublicAccount);
System.out.println(user1.toString());
System.out.println(user2.toString());
System.out.println(user3.toString());
weChatPublicAccount.setDataChanged("午觉睡多久比较好","丁香医生","午觉睡10-20mins是极好的,一觉醒来元气满满");
System.out.println("丁香医生公众号数据更新后...");
System.out.println(user1.toString());
System.out.println(user2.toString());
System.out.println(user3.toString());
}
}
输出:
User1{title='null', author='null', content='null'}
User2{title='null', author='null', content='null'}
User3{title='null', author='null', content='null'}
丁香医生公众号数据更新后...
User1{title='午觉睡多久比较好', author='丁香医生', content='午觉睡10-20mins是极好的,一觉醒来元气满满'}
User2{title='午觉睡多久比较好', author='丁香医生', content='午觉睡10-20mins是极好的,一觉醒来元气满满'}
User3{title='午觉睡多久比较好', author='丁香医生', content='午觉睡10-20mins是极好的,一觉醒来元气满满'}
1.3 节中实现了主题-订阅模型,主题主动推送数据,本节主要介绍观察者主动拉取数据的模型。利用 JDK 自带的 Observable 类和 Observer 接口,设计的方案如类图3所示。
类图3.通过Observable接口(拉)实现微信公众号的主题-订阅模型
与 1.3 节中不同的是,当主题中的数据更新后,并不是主动的将数据推送至所有观察者,而是通知所有观察者,并提供了获取数据的公共方法,以供所有观察者自己获取所需的数据,getTitle()、getAuthor()和getContent(),除此,微信公众号调用的notifyObservers()方法,并不传递数据参数,只负责通知。
WeChatPublicAccount 类:
package headfirst.designpatterns.observer.observablepull;
import java.util.Observable;
public class WeChatPublicAccount extends Observable {
private String title;
private String author;
private String content;
public WeChatPublicAccount() {
}
// 通知所有观察者,可以通过 “拉” 的方式获取自己所需的数据。
public void dataChanged() {
setChanged();
notifyObservers();
}
public void setDataChanged(String title, String author, String content) {
this.title = title;
this.author = author;
this.content = content;
dataChanged();
}
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
public String getContent() {
return content;
}
}
User1类:
package headfirst.designpatterns.observer.observablepull;
import java.util.Observable;
import java.util.Observer;
public class User1 implements Observer {
private String title;
private String author;
private String content;
private Observable weChatPublicAccount;
public User1(Observable weChatPublicAccount) {
this.weChatPublicAccount = weChatPublicAccount;
weChatPublicAccount.addObserver(this);
}
@Override
public String toString() {
return "User1{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
", content='" + content + '\'' +
'}';
}
@Override
public void update(Observable o, Object arg) {
WeChatPublicAccount weChatPublicAccount = (WeChatPublicAccount) o;
title = weChatPublicAccount.getTitle();
author = weChatPublicAccount.getAuthor();
content = weChatPublicAccount.getContent();
}
}
User2 类:
package headfirst.designpatterns.observer.observablepull;
import java.util.Observable;
import java.util.Observer;
public class User2 implements Observer {
private String title;
private String author;
private String content;
private Observable weChatPublicAccount;
public User2(Observable weChatPublicAccount) {
this.weChatPublicAccount = weChatPublicAccount;
weChatPublicAccount.addObserver(this);
}
@Override
public String toString() {
return "User2{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
", content='" + content + '\'' +
'}';
}
@Override
public void update(Observable o, Object arg) {
WeChatPublicAccount weChatPublicAccount = (WeChatPublicAccount) o;
title = weChatPublicAccount.getTitle();
author = weChatPublicAccount.getAuthor();
content = weChatPublicAccount.getContent();
}
}
User3类:
package headfirst.designpatterns.observer.observablepull;
import java.util.Observable;
import java.util.Observer;
public class User3 implements Observer {
private String title;
private String author;
private String content;
private Observable weChatPublicAccount;
public User3(Observable weChatPublicAccount) {
this.weChatPublicAccount = weChatPublicAccount;
weChatPublicAccount.addObserver(this);
}
@Override
public String toString() {
return "User3{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
", content='" + content + '\'' +
'}';
}
@Override
public void update(Observable o, Object arg) {
WeChatPublicAccount weChatPublicAccount = (WeChatPublicAccount) o;
title = weChatPublicAccount.getTitle();
author = weChatPublicAccount.getAuthor();
content = weChatPublicAccount.getContent();
}
}
Test类:
package headfirst.designpatterns.observer.observablepull;
import java.util.Observer;
public class Test {
public static void main(String[] args) {
WeChatPublicAccount weChatPublicAccount = new WeChatPublicAccount();
Observer user1 = new User1(weChatPublicAccount);
Observer user2 = new User2(weChatPublicAccount);
Observer user3 = new User3(weChatPublicAccount);
System.out.println(user1.toString());
System.out.println(user2.toString());
System.out.println(user3.toString());
weChatPublicAccount.setDataChanged("午觉睡多久比较好","丁香医生","午觉睡10-20mins是极好的,一觉醒来元气满满");
System.out.println("丁香医生公众号数据更新后...");
System.out.println(user1.toString());
System.out.println(user2.toString());
System.out.println(user3.toString());
}
}
User1{title='null', author='null', content='null'}
User2{title='null', author='null', content='null'}
User3{title='null', author='null', content='null'}
丁香医生公众号数据更新后...
User1{title='午觉睡多久比较好', author='丁香医生', content='午觉睡10-20mins是极好的,一觉醒来元气满满'}
User2{title='午觉睡多久比较好', author='丁香医生', content='午觉睡10-20mins是极好的,一觉醒来元气满满'}
User3{title='午觉睡多久比较好', author='丁香医生', content='午觉睡10-20mins是极好的,一觉醒来元气满满'}
观察者模式定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新,一个主题,多个观察者。
观察者提供了一种对象设计,让主题和观察者之间松耦合,主题只知道观察者实现了Observer 接口,而具体的观察者也仅依赖Subject 接口或Observable 超类实现注册。
要点:
推
和拉
两种方式更新数据。GitHub 代码地址:https://github.com/SJMP1573/DesignPatterns
[1] Freeman E. Head First 设计模式[M] 中国电力出版社.
[2] 菜鸟教程.