超级链接: Java常用设计模式的实例学习系列-绪论
参考:《HeadFirst设计模式》
观察者模式是一种行为模式。
观察者模式:定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
本文以报纸订阅为场景来学习观察者模式:
用户可以订阅报纸,也可以取消订阅报纸。可以将报纸发布给所有订阅它的用户。不考虑观察者模式,我们针对这个场景进行分析:
因为可以将报纸[Newspaper]发布给所有订阅它的用户[User],所以它需要知道有哪些用户订阅了它,所以: 报纸[Newspaper]需要存储已经订阅它的用户集合[Set<User>]。报纸[Newspaper]需要提供方法给所有订阅者邮寄报纸[mailNewspaperToSubscribers()]。用户[User]需要提供方法用于接受报纸[receiveNewspaper()]。 因为用户可以订阅报纸,也可以取消订阅报纸,所以可以理解为:报纸可以记录谁订阅了它,也可以知道谁取消了订阅 报纸[Newspaper]需要提供方法用于记录谁订阅了它[subscribe()]。报纸[Newspaper]需要提供方法用于记录谁取消了订阅[cancel()]。根据上述分析进行编程。
用户:User
用户[User]需要提供方法用于接受报纸[receiveNewspaper()]。
/** * <p>用户</P> * * @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<User>]。报纸[Newspaper]需要提供方法给所有订阅者邮寄报纸[mailNewspaperToSubscribers()]。报纸[Newspaper]需要提供方法用于记录谁订阅了它[subscribe()]。报纸[Newspaper]需要提供方法用于记录谁取消了订阅[cancel()]。 /** * <p>报纸</P> * * @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
/** * <p>观察者</P> * * @author hanchao */ public interface Observer { /** * 消息更新 */ void update(String message); }观察者实现:User
/** * <p>用户</P> * * @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
/** * <p>主题</P> * * @author hanchao */ public interface Subject { /** * 注册 */ void register(Observer observer); /** * 取消注册 */ void remove(Observer observer); /** * 通知观察者们 */ void notifyObservers(); }主题实现:Newspaper
/** * <p>报纸</P> * * @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
/** * <p>用户</P> * * @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 <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> */ @Override public void update(Observable o, Object arg) { log.info("{} Received Newspaper [{}].", name, arg.toString()); } }被观察者实现:Newspaper
/** * <p>报纸</P> * * @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 <CODE>null</CODE> 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 * <code>hasChanged</code> method, then notify all of its observers * and then call the <code>clearChanged</code> method to indicate * that this object has no longer changed. * <p> * Each observer has its <code>update</code> method called with two * arguments: this observable object and the <code>arg</code> argument. * * @see Observable#clearChanged() * @see Observable#hasChanged() * @see Observer#update(Observable, Object) */ @Override public void notifyObservers() { super.notifyObservers(message); } /** * 标记确定要邮件报纸 * Marks this <tt>Observable</tt> object as having been changed; the * <tt>hasChanged</tt> method will now return <tt>true</tt>. */ @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()什么也不会发生。
最后以UML类图来总结本文的报纸订阅场景以及观察者模式。
