【设计模式】观察者模式

742 阅读5分钟

本文主要介绍观察者模式的概念和用法,以及jdk提供的观察者模式支持。

模式背景

我们在项目开发中会存在当一个对象发生变化的时候,其他的一些对象也需要跟着变更自己的状态或者去做一些操作。比如一个微信公众号,当号主发布了一篇文章之后,所有关注了这个公众号的用户都会受到该文章的推送。这种一个对象的变化,需要引起了多个对象变化的方案可以使用观察者模式。我们可能听得更多的是发布订阅模式(特别是学习消息队列时候),其实也就是观察者模式。

定义&概念

观察者模式:定义对象之间的一种一对多的依赖关系,是的当一个对象状态发生变化的时候,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名有:发布-订阅(publish/subscribe)模式模型-视图(model/view)模式,源-监听(source/listener)模式,从属者(dependents)模式。观察者模式是一种对象行为型模式

原理

观察者模式也是一种简单的设计模式,既然叫观察者模式,那么整个模式中核心的角色自然就2个:观察者被观察者。 现在我们希望被观察者的变化,能够通知到观察者。这里要区分一下主动方和被动方,观察者模式不是说观察者主动去观察对象,如果这样做,势必需要通过不断的循环去检测对象状态是否发生了变化。观察者其实是被动的观察,他唯一的主动就是进行订阅(即告诉被观察者,我要观察你)。所以两者之间的耦合关系就是被观察者内部维系着一系列的观察者对象,当事件发生时,观察者再去通知内部所有的观察者,让观察者触发自己的监听逻辑。

组成要素

  • 目标(Subject)
    • 那个需要被观察的目标对象的抽象,内部有一个集合存储若干个观察者。
    • 提供了一般被观察者对象类的通用模板:注册和注销方法。
  • 具体目标(ConcreteSubject)
    • 目标的实现类或者子类。具体注册和注销的实现逻辑。
  • 观察者(Observer)
    • 观察者类的抽象,一般定义为接口,接口声明了update方法。
    • 观察者通过该抽象进行编程,事件发生后挨个调用观察者的update方法。
  • 具体观察者(ConcreteObserver)
    • 具体的观察者,实现update方法。
    • 我们还可以在里面内持一个目标对象,不仅可以自己来进行自己注册和注销的逻辑,而不需要在客户端进行注册的编写,而且还可以获取到目标对象目前的状态。

jdk观察者模式

观察者模式使用很频繁,地位也很重要。所以JDK提供了对观察者模式的支持。JDK提供了Observer接口和Observable 类,

Observer 代表了抽象观察者:

public interface Observer {
    void update(Observable o, Object arg);
}

Observable 代表了目标类:内部维护的是一个Vector数组来维持着观察者们。

public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;
    public Observable() {
        obs = new Vector<>();
    }
    public synchronized void addObserver(Observer o) {
      //....
    }
    public synchronized void deleteObserver(Observer o) {
      //....
    }
    public void notifyObservers() {
      //....
    }
    //......
    protected synchronized void clearChanged() {
        changed = false;
    }
    public synchronized boolean hasChanged() {
        return changed;
    }
    public synchronized int countObservers() {
        return obs.size();
    }
}

通过这2个类我们可以更方便的在java中使用观察者模式。

MVC

MVC其实也使用了观察者模式。模型层的数据,就是视图层的观察对象,控制器是这2者之间的中介者。

UML

实现

在实现中我们需要分清楚哪些是观察者行为,哪些是目标类的行为。观察者主要是那些需要对别的对象发生变化的时候做出反馈的那些对象,而目标类不一定某个具体实体。比方说游戏里面的队友攻击通知所有队友支援。那么观察者就可以是人,是队友,而目标类就是一个组织,是这个整个队的集合。当一个队友遇到攻击时候,传入整个队,然后让该队通知所有的观察者(队友)。

抽象目标

public abstract class AllyControlCenter {
    /**
     * 战队名
     */
    protected String allyName;
    /**
     * 存储观察者的集合
     */
    protected ArrayList<Observer> players = new ArrayList<>();


    public String getAllyName() {
        return allyName;
    }

    public void setAllyName(String allyName) {
        this.allyName = allyName;
    }


    /**
     * 注册方法,对目标类添加新的观察者
     */
    public void join(Observer obs) {
        System.out.println(obs.getName() + " join " + allyName);
        players.add(obs);
    }

    /**
     * 注销方法:移除观察者
     */
    public void quit(Observer obs) {
        System.out.println(obs.getName() + " leave " + allyName);
        players.remove(obs);
    }

    /**
     * 抽象通知方法,通知所有的观察者
     */
    public abstract void notifyObserver(String name);


}

具体目标

public class ConcreteAllyControlCenter extends AllyControlCenter {

    public ConcreteAllyControlCenter(String name) {
        this.allyName = name;
        System.out.println("create team:" + name);
    }

    @Override
    public void notifyObserver(String name) {
        System.out.println("notify all friends! " + name + " is attacked!");
        for (Observer player : players) {
            // 调用除了自己以外的盟友
            if (!player.getName().equalsIgnoreCase(name)) {
                player.help();
            }
        }
    }
}

抽象观察

public interface Observer {
    String getName();

    void setName(String name);

    /**
     * 帮助队友的方法
     */
    void help();

    /**
     * 申明遭受攻击的方法,即观察者观察到了后,通知哪些对象
     */
    void beAttacked(AllyControlCenter acc);
}

具体观察

public class Player implements Observer {
    private String name;

    public Player(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void help() {
        System.out.println(name + " help you!");
    }

    @Override
    public void beAttacked(AllyControlCenter acc) {
        System.out.println(name + " is attacked!");
        acc.notifyObserver(name);
    }
}

客户端

//创建需要被观察的目标对象
ConcreteAllyControlCenter team = new ConcreteAllyControlCenter("team1");
//定义四个观察者
Observer player1, player2, player3, player4;
player1 = new Player("A");
team.join(player1);
player2 = new Player("B");
team.join(player2);
player3 = new Player("C");
team.join(player3);
player4 = new Player("D");
team.join(player4);
//让某一个遭受攻击
player2.beAttacked(team);

优缺点

优点

  • 观察者和被观察者之间建立了一层抽象耦合,这样观察者容易扩展。被观察者只持有观察者抽象的集合,并不需要知道具体观察者内部的实现。
  • 可以实现表示层和数据逻辑层的分离,支持多播通信,简化了一对多的系统设计难度。
  • 满足开闭原则,可以无需修改代码的方式添加新的观察者。

缺点

  • 如果观察者太多,被观察者通知观察者消耗的时间很多,影响系统的性能。
  • 如果观察者和观察目标之间存在循环依赖,观察目标会触发他们之间进行循环调用而导致系统崩溃。
  • 观察者只能知道被观察者发生了变化,不能知道观察者发生了什么变化。

使用场景

类似于这种发布-订阅,一个对象的变更需要通知给系统中多个对象的情况,都可以考虑使用观察者模式。

总结

观察者模式是一种解决一对多依赖关系的方案,并且这一对多的关系是当一个变化的时候,多个其他对象需要能够对其作出响应。需要清除系统中的观察者和被观察者。被观察者通过内部引用观察者的抽象list来对观察者做统一通知。另外,发布-订阅,MVC,事件驱动都是基于观察者模式。

相关代码:github.com/zhaohaoren/…

如有代码和文章问题,还请指正!感谢!