Java设计模式之观察者模式

一、什么是观察者模式

       观察者模式是对象的行为模式,又叫发布-订阅(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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值