观察者模式(下)

简介: 观察者模式(下)

4.避免添加统一类型的观察者对象


如果我们将测试类代码改为如下方式:

public class Test{
    public static void main(String[] args){
        IObserver obs = new Observer();
        IObserver obs2 = new Observer();
        Subject subject = new Subject();
        subject.register(obs);
        subject.register(obs2);
        subject.setData("dddddddd");
        subject.notifyObservers();
    }
}


可以看出obs 和 obs2观察者对象类型是相同的,都是Observer。

这两个相同类型的观察者对象那个都能正确的添加到主题中。

但是有些情况这是不允许的,要求禁止主题对象添加相同的观察者对象。

因此,在主题对象添加观察者对象前,应该先进行查询,然后判断是否添加观察者对象,register()方法修改后的代码如下:

public  void register(IObserver obs){
    if(!vec.contains(obs)){
        vec.add(obs);
    }
}
// 其他代码略

但是,我们知道Vector中类中的contains()方法默认的是物理查询。 由于Test类中的obs、obs2虽然都是Observer对象,但是他们的物理地址是不同的,因此任然添加到了vec向量中。这种方式也是不可行的。

与我们的初衷不符合,怎么办呢?


我们查看下Vector的contains()方法的源码:

 public boolean contains(Object o) {
      return indexOf(o, 0) >= 0;
    }

我们发现contains方法调用了indexOf():

public synchronized int indexOf(Object o, int index) {
        if (o == null) {
            for (int i = index ; i < elementCount ; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = index ; i < elementCount ; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }


我们发现o.equals(elementData[i]),两个元素相等是由equals()决定的,只要重载传入参数o中的equals()就可以了。

由于elementData[i]也是观察者类的对象,所以equals()方法中的参数原型也明确了。以两个具体观察者为例,功能类代码如下。


主题接口(同上)

public interface ISubject{
    public void register(IObserver obs);
    public void unRegister(IObserver obs);
    public void notifyObservers(); 
}

观察者接口(重点是增加了getMark())

public interface IObserver{
    public void getMark();
    // 推数据的方式
    public void refresh(String data);
}


主题实现类(重点是register方法)

public  class Subject implements ISubject{
// 观察者维护变量
    private Vector<IObserver>  vec = new Vector<IObserver>();
    // 主题中心数据
    private String data ;
    public String getData() {
        return data;
    }
    /**
     * 主题注册(添加)
     * @param data
     */
    public void setData(String data) {
        this.data = data;
    }
    /**
     * 主题注册(添加)观察者
     */
    @Override
    public void register(IObserver obs) {
            if(!vec.contains(obs)){
                vec.add(obs);
                System.out.println("添加成功" );
            }else{
                System.out.println("物理地址相同,请不要添加重复观察者" );
            }
    }
    /**
     *  主题撤销(删除)观察者
     */
    @Override
    public void unRegister(IObserver obs) {
        if(vec.contains(obs)){
            vec.remove(obs);
        }
    }
    /**
     * 主题通知所有观察者进行数据响应
     */
    @Override
    public void notifyObservers() {
        for(int i=0 ;i<vec.size(); i++){
            IObserver obs = vec.get(i);
            obs.refresh(data);
        }
    }
}



观察者接口实现类(重点是重写equals方法)

public class Observer implements IObserver {
    private static final int MARK = 1 ;
    @Override
    public int getMark() {
        return MARK;
    }
    /**
     * 必须要重写equals方法
     */
    @Override
    public boolean equals(Object obj) {
        Observer obs = (Observer)obj;
        return obs.getMark() == MARK ;
    }
    @Override
    public void refresh(String data) {
            System.out.println("Observer1 have received the data :" + data);
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        // 主题实现类
         Subject subject = new Subject();
         // 观察者接口实例化
         IObserver obs = new Observer();
         IObserver obs_copy = new Observer();
         // 注册观察者
        subject.register(obs);
        subject.register(obs_copy);
        // 设置数据
        subject.setData("OOOOOOOOOOOOOOOOOOOOOO");
        // 通知观察者更新数据
        subject.notifyObservers();
    }
}


输出结果:

添加成功
物理地址相同,请不要添加重复观察者
Observer1 have received the data :OOOOOOOOOOOOOOOOOOOOOO


关键思路:

在每个观察者类中增加一个标识常量MARK,不同的观察者对象中的MARK常量值是不同的。 本案例中 Observer中MARK为1 ,如果有Observer2,可以定义为2…….。 由于主题类Subject中register()方法参数是obs是IObserver类型, 是多态表示,因此在IObserver接口中需要增加多态方法getMark(),用于获取观察者对象中的MARK值。


5.反射技术的应用


将观察者类信息封装在XML配置文件中,从而利用反射技术可以动态的加载观察者对象。

配置文件采用键-值配对形式,值对应的是具体观察者的类名称。由于键是关键字,不能重复,为了编程方便,键采用“统一前缀+流水号”的形式。如下说所示:

<?xml version="1.0" encoding="UTF-8"  standalone="no"?>
<!DOCTYPE properties SYSTEM 
"http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>Observer</comment>
    <entry key="observer1">com.turing.designPattern.createPattern.Obsever_Reflect.impl.Observer</entry>
    <entry key="observer2">com.turing.designPattern.createPattern.Obsever_Reflect.impl.Observer2</entry>
</properties>
<!-- 约定好的规则 键前缀 “observer”  键流水号 1  2  3 ..... -->

主题接口:

/**
 * 变化主题可以是泛型的 使用拉数据的方式通知观察者更新数据
 * 
 * @author Mr.Yang
 *
 * @param <T>
 */
public interface ISubject<T> {
    void register(String  path); // 表示从配置文件中加载观察者
    void unRegister(IObserver<T> observer);
    void notifyObservers();
}


观察者接口:

public interface IObserver<T> {
        // 通过拉数据的方式通知观察者更新响应
        void refresh(ISubject<T>  subject);
}


主题实现类:

/**
 * 主题实现类
 * 
 * @author Mr.Yang
 *
 */
public class Subject implements ISubject {
    // 主题数据
    GenericSubjectData data;
    // 观察者集合
    Vector<IObserver> vec = new Vector<>();
    public GenericSubjectData getData() {
        return data;
    }
    public void setData(GenericSubjectData data) {
        this.data = data;
    }
    @Override
    public void register(String path) {
        String prefix = "observer";
        String observerClassName = null;
        try {
            Properties propertie = new Properties();
            FileInputStream fin = new FileInputStream(path);
            propertie.loadFromXML(fin);
            int i = 1;
            while ((observerClassName = propertie.getProperty(prefix + i)) != null) {
                // Constructor c = Class.forName(observerClassName).getConstructor();
                IObserver observer = (IObserver) Class.forName(observerClassName).newInstance();
                vec.add(observer);
                System.out.println("添加" + observerClassName + "成功");
                i++;
            }
    fin.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void unRegister(IObserver observer) {
        if (vec.contains(observer)) {
            vec.remove(observer);
        }
    }
    @Override
    public void notifyObservers() {
        for (int i = 0; i < vec.size(); i++) {
            IObserver observer = vec.get(i);
            // 拉数据的方式,refresh的是ISubject接口类型
            observer.refresh(this);
        }
    }
}

泛型的主题:

/**
 * 根据这一主题进行变化
 * @author Mr.Yang
 *
 */
public class GenericSubjectData {
    // TODO 
}


观察者实现类1:

public class Observer implements IObserver {
    @Override
    public void refresh(ISubject s) {
        Subject sj = (Subject) s;
        GenericSubjectData data = sj.getData();
        System.out.println(
                "观察者【" + Observer.class.getSimpleName() + "】使用拉数据的方式获取主题,更新数据,主题类为:" + data.getClass().getSimpleName());
    }
}

观察者实现类2:

public class Observer2 implements IObserver {
    @Override
    public void refresh(ISubject s) {
        Subject sj = (Subject) s;
        GenericSubjectData data = sj.getData();
        System.out.println(
                "观察者【" + Observer2.class.getSimpleName() + "】使用拉数据的方式获取主题,更新数据,主题类为:" + data.getClass().getSimpleName());
    }
}


测试类:

public class Test {
    public static void main(String[] args) {
        Subject subject = new Subject();
        IObserver<GenericSubjectData>  observer = new Observer();
        subject.setData(new GenericSubjectData());
        subject.register("D:/workspace/ws-java-base/designPatterns/src/observers.xml");
        subject.notifyObservers();
    }
}


输出结果:

添加com.turing.designPattern.createPattern.Obsever_Reflect.impl.Observer成功
添加com.turing.designPattern.createPattern.Obsever_Reflect.impl.Observer2成功
观察者【Observer】使用拉数据的方式获取主题,更新数据,主题类为:GenericSubjectData
观察者【Observer2】使用拉数据的方式获取主题,更新数据,主题类为:GenericSubjectData


JDK中的观察者和设计者模式


由于观察者模式中主题类功能以及观察者接口定义内容的稳定性,JDK的java.utils包提供了系统的主题类Observable以及观察者接口Observer.


UML类图如下:


20151229000810840.png



很明显,Observer相当于上述的IObserver观察者接口,

查看JDK中Observer接口的源码:

/**
 * A class can implement the <code>Observer</code> interface when it
 * wants to be informed of changes in observable objects.
 *
 * @author  Chris Warth
 * @see     java.util.Observable
 * @since   JDK1.0
 */
public interface Observer {
    /**
     * 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>
     *                 method.
     */
    void update(Observable o, Object arg);
}


其中update()方法的第一个参数Observable 类型,表名采用的是“拉”数据的方式;

Observer类(不是接口)相当于上述中的Subject主题类。

查看Observer类的源码结构:

20151229100103818.png


我们可以看出addObserver(),deleteObserver(),notifyObservers()三个方法分别代表 “添加 删除 通知” 观察者对象功能。


XXXChanged()方法主要是设置或者获得changed成员变量的值或状态,changed为true时表明主题中心数据发生了变化。


利用JDK提供的Observer接口 、Observable类来完成观察者模式:


  1. 主题类编写(需要extends Observable类)
import java.util.Observable;
/**
 * 主题实现类,需要继承Observable类
 * 在此类中,设置主题数据,更新标识,通知观察者
 * @author Mr.Yang
 *
 */
public class Subject extends Observable {
    // 主题数据
    private String data ;
    public String getData() {
        return data;
    }
    public void setData(String data) {
        this.data = data; // 更新数据
        setChanged(); // 设置更新数据标志
        notifyObservers(null); // 通知具体观察者
    }
}
  1. 具体观察者类(需要implements Observer接口)
import java.util.Observable;
import java.util.Observer;
/**
 * 具体的观察者,
 * 主要是通过拉数据的方式获取主题,响应数据
 * @author Mr.Yang
 *
 */
public class ObserverImpl implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        Subject s = (Subject)o;
        // 获取主题数据,刷新数据
        String data = s.getData();
        System.out.println(data);
    }
}
  1. 测试
import java.util.Observer;
public class Test {
    public static void main(String[] args) {
        Subject  subject  = new Subject(); // 定义主题实现类
        Observer obs = new ObserverImpl(); // 定义观察者
        subject.addObserver(obs); // 主题添加观察者
        subject.setData("JDK中观察者的实现");// 主题更新数据
    }
}

输出结果:

JDK中观察者的实现

利用JDK中提供的系统类Observable和Observer接口,大大简化了观察者设计模式的程序编码。当然了我们之前提到的那些自定义实现的设计者模式也并非无效的,这些知识是从底层的接口讲起直至最高层,对于理解观察者模式的本质是有必要的。


值得注意的是,JAVA API中给出的Observeable是一个类,不是接口。

尽管该类为它的子类提供了很多可以直接使用的方法,但是有一个问题:Observable的子类无法使用继承方式复用其他类的方法,因为java不支持多继承。 这种情况下,使用我们自定义主题接口ISubject就可以轻松实现了。


Observable 类 和 Observer接口代码分析:


Observable类、Observer接口均是专家级的代码,我们可以从中学到很多。其中有以下两点:

  1. 设置标识变量
  1. 主要体现在changed成员变量的设置。notifyObservers()是非常重要的一个方法,JDK源码如下:
 public void notifyObservers(Object arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Object[] arrLocal;
        synchronized (this) {
            /* We don't want the Observer doing callbacks into
             * arbitrary code while holding its own Monitor.
             * The code where we extract each Observable from
             * the Vector and store the state of the Observer
             * needs synchronization, but notifying observers
             * does not (should not).  The worst result of any
             * potential race-condition here is that:
             * 1) a newly-added Observer will miss a
             *   notification in progress
             * 2) a recently unregistered Observer will be
             *   wrongly notified when it doesn't care
             */
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

可以看出当changed为true时,观察者对象才做出响应。而我们自己设计的程序中,只要调用notifyObservers()方法,即使主题中心数据在没有更新的情况下,观察者对象也能响应。经过对比,我们可以体会到:若某方法非常关键,一定要考虑到它有几种状态,从而定义标识变量来予以控制。


形参的设定

这里的形参主要是指Observable类中的notifyObservers(Object arg)方法参数和Observer接口中定义的update(Observerable ,Object arg) 方法中的第二个形式参数。


有了arg参数对象,我们可以把一些比较信息由主题动态传递给观察者,使编程更加灵活。


举个例子说明一下:


有两个观察者,一个负责统计满足“data=arg”出下的次数,(arg是动态传入的字符串),另一个在屏幕上显示data字符串。


程序如下:

主题类:

import java.util.Observable;
public class SubjectArg extends Observable {
    // 中心数据
    private String data ;
    // 形参
    Object factor ; // 增加条件变量
    public void setFactor(Object factor) {
        this.factor = factor;
    }
    public String getData() {
        return data;
    }
    /**
     * 中心数据改变
     * @param data
     */
    public void setData(String data) {
        this.data = data;
        setChanged(); // 更新标识
        notifyObservers(factor);// 通知具体观察者
    }
}

观察者类1:

import java.util.Observable;
import java.util.Observer;
/**
 * 统计中心数据出现的次数
 * @author Mr.Yang
 *
 */
public class OneObserver implements Observer {
    int  i = 0 ;
    public int getI() {
        return i;
    }
    @Override
    public void update(Observable o, Object arg) {
        SubjectArg subjectArg = (SubjectArg)o;
        if(subjectArg.getData().equals(arg.toString())){
                i++;
        }
    }
}


观察者类2:

import java.util.Observable;
import java.util.Observer;
public class TwoObserver implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        SubjectArg subjectArg = (SubjectArg)o;
        String  msg = subjectArg.getData() ;
        System.out.println("中心数据:" + msg);
    }
}

测试:

public class TestArg {
    public static void main(String[] args) {
        // 实例化主题类   
        SubjectArg subjectArg = new SubjectArg() ;
        // 为主题设置条件参数
        subjectArg.setFactor("hello");
        // 实例化观察者类
        OneObserver observer = new OneObserver();
        TwoObserver observer2 = new TwoObserver();
        // 添加观察者
        subjectArg.addObserver(observer);
        subjectArg.addObsever(observer2);
        // 改变中心数据
        subjectArg.setData("hello");
        subjectArg.setData("hello");
        subjectArg.setData("hello");
        subjectArg.setData("How are  you");
        System.out.println("符合条件的次数:"+observer.getI());
    }
}


输出:

中心数据:hello
中心数据:hello
中心数据:hello
中心数据:How are  you
符合条件的次数:3


相关文章
|
14天前
观察者模式
​ 如有错误或有补充,以及任何的改进意见,请在评论区留下您的高见,同时文中给出大部分示例 如果觉得本文写的不错,不妨点个赞,收藏一下,助力博主产生质量更高的作品 概念 观察者模式是一种对象行为型模式,它定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 在观察者模式中,存在两种类型的对象:目标对象和观察者对象。目标对象负责发出通知,而观察者对象则订阅目标对象,以便在目标对象的状态发生变化时接收到通知。一旦接收到通知,观察者对象就会执行相应的行为。 优缺点 观察者模式的优点主要包括: 解耦:观察者模式有助于降低目标对象和观察者对象
16 0
|
2月前
|
C++
【C++】—— 观察者模式
【C++】—— 观察者模式
|
2月前
|
设计模式 JavaScript 开发者
详细讲解什么是观察者模式
详细讲解什么是观察者模式
|
10月前
|
关系型数据库 API
观察者模式解读
观察者模式解读
|
5月前
行为型 观察者模式(发布订阅)
行为型 观察者模式(发布订阅)
17 0
|
5月前
|
设计模式 Java
【观察者模式】 ——每天一点小知识
【观察者模式】 ——每天一点小知识
|
7月前
5 # 观察者模式
5 # 观察者模式
16 0
|
7月前
|
设计模式 Java Spring
设计模式~观察者模式(Observer)-11
它属于行为型模式的一种。观察者模式定义了一种一对多的依赖关系,一个主题对象可被多个观察者对象同时监听。当这个主题对象状态变化时,会通知所有观察者对象并作出相应处理逻辑。 目录
46 0
|
12月前
|
设计模式
观察者模式(上)
观察者模式(上)
55 0
|
设计模式
我学会了,观察者模式
观察者模式属于行为型模式,这个类型的设计模式总结出了 类、对象之间的经典交互方式,将类、对象的行为和使用解耦了,花式的去使用对象的行为来完成特定场景下的功能。
106 0
我学会了,观察者模式