超级链接: Java常用设计模式的实例学习系列-绪论
参考:《HeadFirst设计模式》
观察者模式是一种行为
模式。
观察者模式:定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
本文以报纸订阅
为场景来学习观察者模式
:
不考虑观察者模式,我们针对这个场景进行分析:
可以将报纸[Newspaper]发布给所有订阅它的用户[User]
,所以它需要知道有哪些用户订阅了它,所以:
报纸[Newspaper]
需要存储已经订阅它的用户集合[Set]
。报纸[Newspaper]
需要提供方法给所有订阅者邮寄报纸[mailNewspaperToSubscribers()]
。用户[User]
需要提供方法用于接受报纸[receiveNewspaper()]
。用户可以订阅报纸,也可以取消订阅报纸
,所以可以理解为:报纸可以记录谁订阅了它,也可以知道谁取消了订阅
报纸[Newspaper]
需要提供方法用于记录谁订阅了它[subscribe()]
。报纸[Newspaper]
需要提供方法用于记录谁取消了订阅[cancel()]
。根据上述分析进行编程。
用户:User
用户[User]
需要提供方法用于接受报纸[receiveNewspaper()]
。
/**
* 用户
*
* @author hanchao
*/
@AllArgsConstructor
@ToString
@EqualsAndHashCode
@Slf4j
public class User {
/**
* 用户编号
*/
@Setter
@Getter
private Integer id;
/**
* 用户名称
*/
@Setter
@Getter
private String name;
/**
* 签收报纸
*/
public void receiveNewspaper(String message) {
log.info("{} Received Newspaper [{}].", name, message);
}
}
报纸:Newspaper
报纸[Newspaper]
需要存储已经订阅它的用户集合[Set]
。报纸[Newspaper]
需要提供方法给所有订阅者邮寄报纸[mailNewspaperToSubscribers()]
。报纸[Newspaper]
需要提供方法用于记录谁订阅了它[subscribe()]
。报纸[Newspaper]
需要提供方法用于记录谁取消了订阅[cancel()]
。/**
* 报纸
*
* @author hanchao
*/
@Slf4j
public class Newspaper {
/**
* 订阅者列表
*/
private Set<User> userSet;
/**
* 报纸信息
*/
@Setter
private String message;
public Newspaper() {
userSet = new HashSet<>();
}
/**
* 订阅
*/
public void subscribe(User user) {
userSet.add(user);
}
/**
* 取消订阅
*/
public void cancel(User user) {
userSet.remove(user);
}
/**
* 向所有订阅者邮寄报纸
*/
public void mailNewspaperToSubscribers() {
userSet.forEach(user -> user.receiveNewspaper(message));
}
}
测试代码
public static void main(String[] args) {
//开始有2个人订阅报纸
Newspaper newspaper = new Newspaper();
newspaper.subscribe(new User(1, "Lily"));
User jack = new User(2, "Jack");
newspaper.subscribe(jack);
//邮寄报纸
newspaper.setMessage("中国青年报");
newspaper.mailNewspaperToSubscribers();
System.out.println("--------------------------------------");
//后来,有一个人取消了订阅报纸
newspaper.cancel(jack);
//邮寄报纸
newspaper.setMessage("环球时报");
newspaper.mailNewspaperToSubscribers();
}
测试结果:
2019-07-24 17:51:17,520 INFO - Lily Received Newspaper [中国青年报].
2019-07-24 17:51:17,522 INFO - Jack Received Newspaper [中国青年报].
--------------------------------------
2019-07-24 17:51:17,522 INFO - Lily Received Newspaper [环球时报].
缺点:
依赖倒置原则(面向接口的编程)
。其实第2章的原始实现就是观察者模式的雏形,只不过未遵从依赖倒置原则(面向接口的编程)
。
为了统一规范,提高普适性,约定俗成的
,在观察者模式中,我们常用观察者Observer
、主题Subject
、消息更新update()
、注册register()
、取消注册remove()
、通知观察者notifyObservers()
等称呼。
下面按照观察者模式的约定,进行一遍实现。
用户[User]
抽象出接口观察者[Observer]
。报纸[Newspaper]
抽象出接口主题[Subject]
。观察者抽象:Observer
/**
* 观察者
*
* @author hanchao
*/
public interface Observer {
/**
* 消息更新
*/
void update(String message);
}
观察者实现:User
/**
* 用户
*
* @author hanchao
*/
@AllArgsConstructor
@Slf4j
@ToString
@EqualsAndHashCode
public class User implements Observer{
/**
* 用户编号
*/
@Setter
@Getter
private Integer id;
/**
* 用户名称
*/
@Setter
@Getter
private String name;
/**
* 签收报纸
*/
@Override
public void update(String message) {
log.info("{} Received Newspaper [{}].", name, message);
}
}
主题抽象:Subject
/**
* 主题
*
* @author hanchao
*/
public interface Subject {
/**
* 注册
*/
void register(Observer observer);
/**
* 取消注册
*/
void remove(Observer observer);
/**
* 通知观察者们
*/
void notifyObservers();
}
主题实现:Newspaper
/**
* 报纸
*
* @author hanchao
*/
@Slf4j
public class Newspaper implements Subject {
/**
* 订阅者列表
*/
private Set<Observer> observerSet;
/**
* 报纸信息
*/
@Setter
private String message;
public Newspaper() {
observerSet = new HashSet<>();
}
/**
* 订阅
*
* @param observer 订阅者
*/
@Override
public void register(Observer observer) {
observerSet.add(observer);
}
/**
* 取消订阅
*
* @param observer 订阅者
*/
@Override
public void remove(Observer observer) {
observerSet.remove(observer);
}
/**
* 向所有订阅者邮寄报纸
*/
@Override
public void notifyObservers() {
observerSet.forEach(user -> user.update(message));
}
}
测试代码:
public static void main(String[] args) {
//开始有2个人订阅报纸
Newspaper newspaper = new Newspaper();
newspaper.register(new User(1, "Lily"));
User jack = new User(2, "Jack");
newspaper.register(jack);
//邮寄报纸
newspaper.setMessage("中国青年报");
newspaper.notifyObservers();
System.out.println("--------------------------------------");
//后来,有一个人取消了订阅报纸
newspaper.remove(jack);
//邮寄报纸
newspaper.setMessage("环球时报");
newspaper.notifyObservers();
}
Java本身提供了内置的观察者模式,位于rt.jar
包中:
Observer
接口Observable
类,注意,这是一个类。下面针对报纸订阅场景进行内置观察者实现:
用户[User]
实现父接口观察者[Observer]
。报纸[Newspaper]
继承父类被观察者[Observable]
。观察者实现:User
/**
* 用户
*
* @author hanchao
*/
@AllArgsConstructor
@Slf4j
@ToString
@EqualsAndHashCode
public class User implements Observer {
/**
* 用户编号
*/
@Setter
@Getter
private Integer id;
/**
* 用户名称
*/
@Setter
@Getter
private String name;
/**
* 签收报纸
* This method is called whenever the observed object is changed. An
* application calls an Observable object's
* notifyObservers
method to have all the object's
* observers notified of the change.
*
* @param o the observable object.
* @param arg an argument passed to the notifyObservers
*/
@Override
public void update(Observable o, Object arg) {
log.info("{} Received Newspaper [{}].", name, arg.toString());
}
}
被观察者实现:Newspaper
/**
* 报纸
*
* @author hanchao
*/
@Slf4j
public class Newspaper extends Observable {
/**
* 报纸信息
*/
@Setter
private String message;
/**
* 订阅
* Adds an observer to the set of observers for this object, provided
* that it is not the same as some observer already in the set.
* The order in which notifications will be delivered to multiple
* observers is not specified. See the class comment.
*
* @param o an observer to be added.
* @throws NullPointerException if the parameter o is null.
*/
@Override
public synchronized void addObserver(Observer o) {
super.addObserver(o);
}
/**
* 取消订阅
* Deletes an observer from the set of observers of this object.
* Passing null
to this method will have no effect.
*
* @param o the observer to be deleted.
*/
@Override
public synchronized void deleteObserver(Observer o) {
super.deleteObserver(o);
}
/**
* 向所有订阅者邮寄报纸
* If this object has changed, as indicated by the
* hasChanged
method, then notify all of its observers
* and then call the clearChanged
method to indicate
* that this object has no longer changed.
*
* Each observer has its update
method called with two
* arguments: this observable object and the arg
argument.
*
* @see Observable#clearChanged()
* @see Observable#hasChanged()
* @see Observer#update(Observable, Object)
*/
@Override
public void notifyObservers() {
super.notifyObservers(message);
}
/**
* 标记确定要邮件报纸
* Marks this Observable object as having been changed; the
* hasChanged method will now return true.
*/
@Override
protected synchronized void setChanged() {
super.setChanged();
}
}
测试代码
public static void main(String[] args) {
//开始有2个人订阅报纸
//Observable newspaper = new Newspaper(); 不能这么用
Newspaper newspaper = new Newspaper();
newspaper.addObserver(new User(1, "Lily"));
Observer jack = new User(2, "Jack");
newspaper.addObserver(jack);
//邮寄报纸
newspaper.setMessage("中国青年报");
newspaper.setChanged();
newspaper.notifyObservers();
System.out.println("--------------------------------------");
//换一份报纸
//邮寄报纸
newspaper.setMessage("环球时报");
newspaper.notifyObservers();
}
测试结果
2019-07-24 18:01:29,521 INFO - Jack Received Newspaper [中国青年报].
2019-07-24 18:01:29,523 INFO - Lily Received Newspaper [中国青年报].
--------------------------------------
注意:
这里需要先调用newspaper.setChanged();
,然后再调用newspaper.notifyObservers();
。
setChanged()
方法的含义是:表示消息发生了变化。
如果不显示调用setChanged()
,即使消息发生了变化,调用notifyObservers()
什么也不会发生。