设计模式--观察者模式
来源:互联网 发布:java super this 编辑:程序博客网 时间:2024/06/03 04:37
一、什么是观察者模式
观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
举个例子:一个老师通知三个学生写作业,只要老师一发作业通知了,三个学生就会收到通知并进行处理。
什么时候使用观察者模式:
1、 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用;
2、当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变;
3、当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的;
比如账户改变了发送短信;用户操作了记录操作行为等;
观察者模式所涉及的角色有:
● 抽象主题(Subject)角色:抽象主题角色把所有观察者对象的保存在一个集合里(比如ArrayList对象),每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。
● 具体主题(ConcreteSubject)角色:(抽象主题角色的实现类)将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。
● 抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
● 具体观察者(ConcreteObserver)角色:(抽象观察者角色的实现类)存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。
二、例子
写老师通知学生写作业的例子,根据以上观察者模式角色可知通常有两个接口及其实现类(抽象主题和观察者角色接口及两者的实现类,老师是主题角色,学生是观察者角色)。下面开始代码:
1、抽象观察者角色接口:提供观察者接收到通知修改状态的方法;
/** * 观察者 */public interface Observer { //当主题发送通知给观察者时,观察者改变状态 void update(String info);}
2、抽象主题角色接口:提供增加/删除/通知观察者的方法;
/** * 主题接口 */public interface Subject { void addObserver(Observer observer); void removeObserver(Observer observer); void notifyObserver(String info);}
3、主题接口实现类:提供一个集合存放观察者,提供一个变量提供信息给观察者相应信息;
/** * 主题接口实现类 */public class TeacherSubject implements Subject { //用来存放和记录观察者 private List<Observer> observers = new ArrayList<Observer>(); //记录状态的字符串 private String info; @Override public void addObserver(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { int i = observers.indexOf(observer); if (i >= 0) { observers.remove(i); } } @Override public void notifyObserver(String info) { System.out.println("今天的作业是" + info); for (Observer observer : observers) { observer.update(info); } }}
4、观察者接口实现类:如果不考虑取消观察,可以不提供主题角色变量(在构造观察者对象的时候将观察者放到集合里)
/** * 观察者接口实现类 */public class StudentObserver implements Observer { //保存一个主题对象的引用,以后如果想取消订阅,有了这个引用会比较方便 private TeacherSubject t; //学生的姓名,用来标识不同的学生对象 private String name; //构造器用来注册观察者 public StudentObserver(String name, TeacherSubject t) { this.name = name; this.t = t; //每新建一个学生对象,默认添加到观察者的行列 t.addObserver(this); } @Override public void update(String info) { System.out.println(name + "收到作业:" + info); }}
5、测试类:一个老师,通知三个学生
public class TestObserver { public static void main(String[] args) { TeacherSubject teacher = new TeacherSubject(); StudentObserver zhangSan = new StudentObserver("张三", teacher); StudentObserver liSi = new StudentObserver("李四", teacher); StudentObserver wangWu = new StudentObserver("王五", teacher); teacher.notifyObserver("第二页第六题"); teacher.notifyObserver("第三页第七题"); teacher.notifyObserver("第五页第八题"); }}
以上例子很明显透露出观察者模式的一个缺点:循环!
三、推模型和拉模型
在观察者模式中,又分为推模型和拉模型两种方式。
● 推模型
主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。
● 拉模型
主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
两种模式的比较:
推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值;
推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
拉模式具体代码区别就是传的参数,代码不提供了。
四、JAVA提供的对观察者模式的支持
java.util库里面,提供了一个Observable类以及一个Observer接口,构成JAVA语言对观察者模式的支持。
1、Observer接口:
抽象观察者接口,该接口只定义了一个方法,即update()方法,当被观察者对象的状态发生变化时,被观察者对象的notifyObservers()方法就会调用这一方法;
2、Observable抽象类:
被观察者类(主题角色类)都是java.util.Observable类的子类。
使用javaAPI的观察者模式需要明白这么几件事情:
1、如何使对象变为观察者?
实现观察者接口(java.util.Observer),然后调用Observable对象的addObserver()方法,不想再当观察者时,调用deleteObserver()就可以了;
2、被观察者(主题)如何发出通知?
第一步:先调用setChanged()方法,标识状态已经改变的事实;
第二步:调用notifyObservers()方法或notifyObservers(Object arg),这就牵扯到推(push)和拉(pull)的方式传送数据。如果想用push的方式”推”数据给观察者,可以把数据当做数据对象传送给notifyObservers(Object arg)方法,其中的arg可以为任意对象,意思是你可以将任意对象传送给每一个观察者。如果调用不带参数的notifyObserver()方法,则意味着你要使用pull的方式去主题对象中”拉”来所需要的数据;
3、观察者如何接收通知?
观察者只需要实现一个update(Observable o,Object arg)方法,第一个参数o,是指定通知是由哪个主题下达的,第二个参数arg就是上面notifyObserver(Object arg)里传入的数据,如果不传该值,arg为null;
下面使用java内置API实现上面我所写的老师和学生的例子:
1、主题角色类(继承Observable类)
public class Teacher extends Observable { private String info; public void setHomework(String info) { this.info = info; System.out.println("布置的作业是" + info); setChanged(); notifyObservers();//这里使用了拉模式观察,如果使用推模式可以在改方法传参数,会在观察者update方法的第二个参数获取到 } public String getInfo() { return info; }}
2、观察者对象类(实现Observer接口)
public class Student implements Observer { private Observable ob; private String name; public Student(String name, Observable ob) { this.ob = ob; this.name = name; ob.addObserver(this); } @Override public void update(Observable o, Object arg) { Teacher t = (Teacher) o; System.out.println(name + "收到作业信息:" + t.getInfo()); }}
3、测试方法类似,就不写了
五、优缺点
优点:
1、观察者模式支持广播通讯。被观察者会向所有的登记过的观察者发出通知;
2、观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口;
缺点:
1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间;
2、如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式是要特别注意这一点;
参考文档:
1、https://www.cnblogs.com/fingerboy/p/5468994.html
2、https://www.cnblogs.com/java-my-life/archive/2012/05/16/2502279.html