首页 热点专区 小学知识 中学知识 出国留学 考研考公
您的当前位置:首页正文

【重温设计模式】JAVA中对观察者模式的支持

来源:要发发知识网

前言

本文将分为以下三个部分介绍观察者模式

  • 1.观察者模式介绍
  • 2.观察者模式示例
  • 3.Java中的观察者模式

一.观察者模式介绍

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

image

观察者模式的三要素:观察者,被观察者,事件

优点:
1、解耦,被观察者只知道观察者列表「抽象接口」,被观察者不知道具体的观察者
2、被观察者发送通知,所有注册的观察者都会收到信息「可以实现广播机制」

缺点:
1、如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
2、观察者知道被观察者发送通知了,但是观察者不知道所观察的对象具体是如何发生变化的
3、如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

二.观察者模式示例

1.警察抓小偷

  • 1)首先是被观察者(罪犯)接口的定义
//罪犯
public interface Criminal {
    //罪犯被顶上了
    void spotted(Police police);
    //罪犯的犯罪行为
    void crime(String illegalAction);
    String getName();
    String getIllegalAction();
}

2)接下来是观察者(警察)接口的定义

//警察
public interface Police {
    //警察的行为
    void action(Criminal criminal);
}

  • 3)然后是被观察者(小偷)的实现类
//小偷
public class Thief implements Criminal {
    //盯上罪犯的警察集合
    private LinkedList<Police> polices = new LinkedList<>();
    //罪犯姓名
    private String name;
    //犯罪行为
    private String illegalAction;

    //单例模式
    public static Thief getInstance(){
        return InnerClass.THIEF;
    }
    //静态内部类单例模式
    private static class InnerClass{
        private static final Thief THIEF = new Thief("小偷");
    }

    private Thief(String name) {
        this.name = name;
    }

    @Override
    //被盯上了
    public void spotted(Police police) {
        if (!polices.contains(police)) {
            polices.add(police);
        }
    }

    @Override
    //犯罪
    public void crime(String illegalAction) {
        this.illegalAction = illegalAction;
        for (Police police : polices) {
            police.action(this);
        }
    }

    @Override
    //罪犯姓名
    public String getName() {
        return name;
    }

    @Override
    //犯罪行为
    public String getIllegalAction() {
        return illegalAction;
    }
}

  • 4)最后是观察者(警察)的实现类

武警

//武警
public class ArmedPolice implements Police {

    public ArmedPolice(Criminal criminal) {
        System.out.println(criminal.getName()+"被人民武警盯上了");
        //这里调用罪犯的spotted方法,说明罪犯被顶上了
        criminal.spotted(this);
    }

    @Override
    public void action(Criminal criminal) {
        System.out.println("武警发现了"+criminal.getName()+criminal.getIllegalAction());
    }
}

朝阳群众

//朝阳群众
public class MassesPolice implements Police {
    public MassesPolice(Criminal criminal) {
        System.out.println(criminal.getName()+"被朝阳大妈盯上了");
        //这里调用罪犯的spotted方法,说明罪犯被顶上了
        criminal.spotted(this);
    }

    @Override
    public void action(Criminal criminal) {
        System.out.println("朝阳大妈发现了"+criminal.getName()+criminal.getIllegalAction());
    }
}

公安

//公安
public class SecurityPolice implements Police {

    public SecurityPolice(Criminal criminal) {
        System.out.println(criminal.getName()+"被公安盯上了");
        //这里调用罪犯的spotted方法,说明罪犯被顶上了
        criminal.spotted(this);
    }

    @Override
    public void action(Criminal criminal) {
        System.out.println("公安发现了"+criminal.getName()+criminal.getIllegalAction());
    }
}
  • 5)测试
public class TestObserver {
    public static void main(String[] args) {
        Thief thief = Thief.getInstance();
        ArmedPolice armedPolice = new ArmedPolice(thief);
        SecurityPolice securityPolice = new SecurityPolice(thief);
        MassesPolice massesPolice = new MassesPolice(thief);

        System.out.println("——————————————————————————");
        thief.crime("盗窃");

    }
}
运行效果截图.png

2.小明和妈妈

生活中的一个例子,小明妈妈做好了饭,让小明去地里叫他爸回来吃饭,小明说好的我马上去,过了半个小时小明和他爸一起来了,小明给妈妈的说:“妈,爸回来了”,妈妈说:“好的我知道了,让你爸洗洗手吃饭吧”。
在这一过程中,小明是被观察者,小明他妈是观察者。
小明观察到他爸回来了,这个过程就会触发一个事件,然后通知观察者(他妈)。

image
  • 1)定义一个回调接口
image
  • 2)定义妈妈类
image
  • 3)定义小明类
image
  • 4)测试
image
  • 5)运行查看结果
image

三.Java中的观察者模式

观察者模式

在上图的结构,其中Observer作为观察者,Observable则作为被观察者,Observable的状态改变会给注册的Observer进行通知,考虑易用和低耦合,保证高度的协作。

1.代码展示

MyObservable非常简单,只是继承了Observable:

public class MyObservable extends Observable {

    public static void main(String[] args) {
        //定义被观察者
        MyObservable myObservable = new MyObservable();
        //建立被观察者和观察者间的关系
        myObservable.addObserver(new MyObserver());
        //这里需要注意 让其触发事件时及时通知
        myObservable.setChanged();
        //触发事件,通知观察者
        myObservable.notifyObservers("helll");
    }
}

此处为了简化结构,直接将main方法写在MyObservable中用于进行效果的测试,main中我们先实例化了MyObservable类,并且将一个MyObserver实例用addObserver方法加入它的观察者列表中,然后通过setChanged改变changed的状态值,随后便可以notifyObservers,里面可以带参数做消息传递,也可以不带参数。关于changed,是因为只有该变量为true的情况下,才会进行通知,否则notifyObservers是无法通知到具体Observer的。

MyObserver的结构也非常简单,由于继承了Observer接口,因此需要实现它的抽象方法update,这个方法也是用于接收Observable发来的通知并执行相应的处理逻辑的:

public class MyObserver implements Observer{

    public void update(Observable o, Object arg) {
        System.out.println("观察对象为:" + o + ";传递参数为:" + arg);
    }
}

运行一下结果为:

JAVA中对观察者模式的支持.png

而在注释了setChanged方法之后,会发现没有任何输出,可以理解为,并没有真正通知到Observer。

2.原理解析

原理非常简单,查看Observable源码即可。其实从类图中就可以看出一些端倪,Observable中只有两个变量,一个是changed,一个是obs,从之前的分析可以看出changed只是一个状态指示的变量,那么obs应该就是一个用于存储Observer实例的变量了,它具体是用一个Vector来进行存储的。

private boolean changed = false;
private Vector<Observer> obs;

这样的情况下,我们很容易就可以猜测,obs的初始化应该是在Observable的构造方法中实现的,果不其然,构造方法也仅仅只是完成了obs的初始化:

public Observable() {
        obs = new Vector<>();
    }

然后我们更为关心的是Observable与Observer之间的关系,那么就是重点关注两个方法,addObserver和notifyObservers。addObserver很显然,里面的主要操作是将作为参数传入的Observer加入到Vector中:

public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

notifyObservers则是逐一通知Observer的过程,不过在这个方法中会检查changed的值,以及会在通知完成后对changed归零(其中clearChanged方法就是将changed变量重新置为false

public void notifyObservers(Object arg) {
        Object[] arrLocal;

        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

到这里基本上整个基于jdk实现的观察者模式的原理已经非常明了了,另外值得注意的一个点是,这个实现中用了一些措施来使它线程安全。比如,使用线程安全的容器Vector,addObserver方法进行了枷锁,在notifyObservers方法中,有一个操作进行了加锁

synchronized (this) {
    if (!changed)
        return;
    arrLocal = obs.toArray();
    clearChanged();
}

这里主要确保了对changed变量的验证和清零操作以及将obs中的元素赋值给arrLocal的过程是原子操作,这里减少了锁的粒度,提高并发性,也因此大家能明白为何需要arrLocal这个变量了,随后对arrLocal数组进行便利并逐一通知则不需要加锁了,因为arrLocal已经是一个本地变量,并未暴露。

总结

1.使用场景
一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。

一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。

一个对象必须通知其他对象,而并不知道这些对象是谁。

需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

2.注意事项

  • JAVA 中已经有了对观察者模式的支持类。
  • 避免循环引用。
  • 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

参考:

显示全文