观察者模式又称为发布/订阅(Publish/Subscribe)模式,观察者模式定义了对象间的一种一对多依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

观察者(Observer)相当于事件监听者,被观察者(Observable)相当于事件源和事件,将观察者和被观察者的对象分离开,提高了应用程序的可维护性和重用性。执行逻辑时通知observer即可触发oberverupdate,同时可传被观察者和参数。

观察者Observer:所有潜在的观察者必须实现观察者接口,这个接口只有update方法,当主题改变时,它被调用。

具体观察者ConcreteObserver: 具体观察者可以是任何实现了Observer接口的类。观察者必须注册具体主题,一边接收更新。

可观察者Subject: 主题接口,即可观察者Observable,对象使用此接口注册为观察者,或者把自己从观察者中删除,每个主题可以有多个观察者。

具体可观察者ConcreteSubject: 一个具体主题实现了主题接口,除了注册和撤销之外,具体主题还实现了notifyObservers()方法,这个方法用来在主题状态改变时更新所有观察者。具体主题也可能有设置和获取状态的方法。

Subject(被观察者)包含了一些需要在其状态改变时通知的观察者。因此,他应该提供给观察者可以register(注册)自己和unregister(注销)自己的方法。当Subject(被观察者)发生变化的时候,也需要包含一个方法来通知所有观察者。当通知观察者的时候,可以推送更新内容,或者提供另外一个方法来获得更新内容。参考资料:https://www.javacodegeeks.com/2013/08/observer-design-pattern-in-java-example-tutorial.html

JAVA提供了内置的方式来实现观察者模式,java.util.Observablejava.util.Observer接口。然而他们用的不是很广泛。因为此实现过于简单,大多数时候我们都不想最后扩展的类仅仅是实现了观察者模式,因为JAVA类不能多继承。

Java Messages Service(JMS)消息服务使用观察者模式与命令模式来实现不同的程序之间的数据的发布和订阅。MVC模型-视图-控制框架也使用观察者模式,把模型当做被观察者,视图视为观察者。视图能够注册自己到模型上来获得模型的改变。

观察者模式实例

在此实例中,将完成一个简单的主题讨论,观察者能够注册此主题。任何在此主题上的内容提交导致的变化都会通知所有注册的观察者。基于Subject被观察者的需求,这个是实现一个基本的Subject接口,此接口定了一系列具体的方法需要在随后实现接口的具体类中被实现。

package com.journaldev.design.observer;
public interface Subject {
    
    //methods to register and unregister observers
    public void register(Observer obj);
    
    public void unregister(Observer obj);
    
    //method to notify observers of change
    public void notifyObservers();

    //method to get updates from subject
    public Object getUpdate(Observer obj);
    
    
    
}

现在创建一个相关联的观察者。它需要有一个方法能使Subject附属于一个观察者。另外的方法能够接受Subject的变化通知。

package com.journaldev.design.observer;
 
public interface Observer {
    
    //method to update the observer, used by subject
    public void update();
    
    //attach with subject to observe
    public void setSubject(Subject sub);
    
}

这种关联已经建立。现在实现具体的主题。

package com.journaldev.design.observer;
import java.util.ArrayList;
import java.util.List;
public class MyTopic implements Subject {
    
    private List<Observer> observers;
    
    private String message;
    
    private boolean changed;
    
    private final Object MUTEX= new Object();
    
    public MyTopic(){
    
this.observers=new ArrayList<>();
    
    }
    
    @Override
    public void register(Observer obj) {
    
if(obj == null) throw new NullPointerException("Null Observer");
    
if(!observers.contains(obj)) observers.add(obj);
    
    }
    
    @Override
    public void unregister(Observer obj) {
    
observers.remove(obj);
    
    }
    
    @Override
    public void notifyObservers() {
    
List<Observer> observersLocal = null;
    
//synchronization is used to make sure any observer registered after message is received is not notified
synchronized (MUTEX) {
    
    if (!changed)
   return;
    observersLocal = new ArrayList<>(this.observers);
    this.changed=false;
    
}
    
for (Observer obj : observersLocal) {
    obj.update();
}
    }
    
    @Override
    public Object getUpdate(Observer obj) {
return this.message;
    }
    
    //method to post message to the topic
    public void postMessage(String msg){
System.out.println("Message Posted to Topic:"+msg);
this.message=msg;
this.changed=true;
notifyObservers();
    
    }
    
}

注册与注销观察者方法的实现非常简单,额外的方法postMessage()将被客户端应用来提交一个字符串消息给此主题。注意,布尔变量用于追踪主题状态的变化并且通知观察者此种变化。这个变量是必须的,因为如果没有更新,但是有人调用notifyObservers()方法,他就不能发送错误的通知信息给观察者。

此外需要注意的是,notifyObservers()中使用synchronization同步的方式来确保在消息被发布给主题之前,通知只能被发送到注册的观察者处。此处是观察者的实现。他们将一直关注subject对象。

package com.journaldev.design.observer;
public class MyTopicSubscriber implements Observer {
    
    private String name;
    
    private Subject topic;
    
    public MyTopicSubscriber(String nm){
this.name=nm;
    }
    
    @Override
    public void update() {
    
String msg = (String) topic.getUpdate(this);
if(msg == null){
    System.out.println(name+":: No new message");
}else
System.out.println(name+":: Consuming message::"+msg);
    
    }
    
    @Override
    public void setSubject(Subject sub) {
this.topic=sub;
    }
}

注意,update()方法的实现使用了被观察者的getUpdate()来处理更新的消息。此处应该避免把消息作为参数传递给update()方法。

下面是一个简单的测试程序来验证话题类的实现。

package com.journaldev.design.observer;
public class ObserverPatternTest {

    public static void main(String[] args) {
    
//create subject
MyTopic topic = new MyTopic();

//create observers
Observer obj1 = new MyTopicSubscriber("Obj1");
Observer obj2 = new MyTopicSubscriber("Obj2");
Observer obj3 = new MyTopicSubscriber("Obj3");

//register observers to the subject
topic.register(obj1);
topic.register(obj2);
topic.register(obj3); 
    
//attach observer to subject
obj1.setSubject(topic);    
obj2.setSubject(topic);
obj3.setSubject(topic);

//check if any update is available
obj1.update();

//now send message to subject
topic.postMessage("New Message");
    
    }
}

此处为上述输出内容:

Obj1:: No new message
Message Posted to Topic:New Message
Obj1:: Consuming message::New Message
Obj2:: Consuming message::New Message
Obj3:: Consuming message::New Message

观察者模式的UML图

image.png

观察者模式也被叫做发布订阅模式。JAVA中的一些具体应用如下:

Swing java.util.EventListener
javax.servlet.http.HttpSessionBindingListener
javax.servlet.http.HttpSessionAttributeListener

关注下方微信公众号“Java精选”(w_z90110),回复关键词领取资料:如Mysql、Hadoop、Dubbo、Spring Boot等,免费领取视频教程、资料文档和项目源码。

Java精选专注程序员推送一些Java开发知识,包括基础知识、各大流行框架(Mybatis、Spring、Spring Boot等)、大数据技术(Storm、Hadoop、MapReduce、Spark等)、数据库(Mysql、Oracle、NoSQL等)、算法与数据结构、面试专题、面试技巧经验、职业规划以及优质开源项目等。其中一部分由小编总结整理,另一部分来源于网络上优质资源,希望对大家的学习和工作有所帮助。

评论

  1. #1

    科科 (2018/05/22 11:05:41)回复
    为什么要避免将消息作为参数给update()方法?

    路人甲 (2018/05/29 13:38:27)回复
    已经处理了不需要再次处理消息,(实现使用了被观察者的getUpdate()来处理更新的消息)请认真阅读。

  2. #2

    机智的小盆友 (2017/11/14 16:11:47)回复
    赶脚和 那个 什么 activiMq 很相似啊 ,有木有 就是 消息发布,那边 监听 ,我只是哥萌新,如有说的不当,你们来 打我啊 ,O(∩_∩)O哈哈~

    路人甲 (2018/05/29 13:33:58)回复
    MQ消息队列还存在,点对点模式。

分享:

支付宝

微信