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类图如下:
很明显,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类的源码结构:
我们可以看出addObserver(),deleteObserver(),notifyObservers()三个方法分别代表 “添加 删除 通知” 观察者对象功能。
XXXChanged()方法主要是设置或者获得changed成员变量的值或状态,changed为true时表明主题中心数据发生了变化。
利用JDK提供的Observer接口 、Observable类来完成观察者模式:
- 主题类编写(需要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); // 通知具体观察者 } }
- 具体观察者类(需要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); } }
- 测试
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接口均是专家级的代码,我们可以从中学到很多。其中有以下两点:
- 设置标识变量
- 主要体现在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