设计模式-观察者模式-以报纸订阅为例

超级链接: Java常用设计模式的实例学习系列-绪论

参考:《HeadFirst设计模式》


1.关于观察者模式

观察者模式是一种行为模式。

观察者模式:定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。

本文以报纸订阅为场景来学习观察者模式

  • 用户可以订阅报纸,也可以取消订阅报纸。
  • 可以将报纸发布给所有订阅它的用户。

2.原始实现

不考虑观察者模式,我们针对这个场景进行分析:

  • 因为可以将报纸[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 [环球时报]. 

缺点:

  • 未遵从依赖倒置原则(面向接口的编程)

3.观察者模式实现

其实第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();
    }

4.Java内置观察者模式

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()什么也不会发生。

5.总结

最后以UML类图来总结本文的报纸订阅场景以及观察者模式。
设计模式-观察者模式-以报纸订阅为例_第1张图片

你可能感兴趣的:(Java设计模式)