设计模式--观察者模式

来源:互联网 发布: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

阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 1060铝板多少钱一吨 5052合金铝板 2.0铝板多少钱一平方 3003铝板价格 5052铝板多少钱一公斤 木纹转印铝板 外墙装饰铝板价格 铝皮价格 太空铝毛巾架厂家 太空铝毛巾架 太空铝毛巾架浴巾架 太空铝毛巾架厂 铝框行李箱改密码图解 铝框行李箱怎么设置密码图解 铝框拉杆箱怎么设置密码图解 铝框行李箱扣不上图解 铝框行李箱设密码教程图解 拉链和铝框哪个更耐用 铝框玻璃门 铝框门 铝框行李箱设密码教程 铝框行李箱扣住的教程 铝框行李箱锁扣图解 铝框拉杆箱怎么改密码图解 铝框型材 铝框箱 铝框行李箱 拉链和铝框哪个更实用 铝框和拉链的拉杆箱哪种更好 铝框行李箱锁扣维修教程 铝框行李箱密码忘了怎么办 铝框行李箱打不开图解 拉杆箱拉链款好还是铝框款好 包装桶 涂料桶 铝棒 铝棒重量计算公式 7075t651铝棒 7075铝棒 铝棒加热炉 3003铝棒