观察者模式(Observer Pattern)

一、什么是观察者模式

观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象(目标对象)的状态发生改变时,所有依赖于它的对象(观察对象)都得到通知并被自动更新。特点:被观察者和观察者一般是一对多的关系,一个被观察者对应多个观察者,当一个被观察者的状态发生改变时,被观察者通知观察者,然后可以在观察者内部进行业务逻辑的处理。

JDK 提供了 一套观察者模式的实现,在java.util包中, java.util.Observable类和java.util.Observer接口。Observable 是被观察者,Observer 是观察者。

在观察者模式中,主题是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。观察者模式不仅被广泛应用于软件界面元素之间的交互,在业务对象之间的交互、权限管理等方面也有广泛的应用。

面向对象设计的一个原则是:系统中的每个类将重点放在某一个功能上,而不是其他方面。一个对象只做一件事情,并且将其做好。观察者模式完美的将观察者和被观察的对象分离开,在模块之间划定了清晰的界限,提高了应用程序的可维护性和重用性。

观察者模式,又叫发布-订阅模式(Publish/Subscribe Pattern)。UML 结构图如下:

1️⃣【主题 Subject通知的发布者】它把所有对观察者对象的引用文件存在了一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供了一个接口,可以增加和删除观察者对象。

首先定义一个观察者数组,并实现增、删及通知操作。它的职责很简单,就是定义谁能观察,谁不能观察,用 Vector 是线程同步的,比较安全,也可以使用 ArrayList,是线程异步的,但不安全。

public class Subject {
    //观察者数组
    private Vector<Observer> oVector = new Vector<>();
    //增加一个观察者
    public void addObserver(Observer observer) {
        this.oVector.add(observer);
    }
    //删除一个观察者
    public void deleteObserver(Observer observer) {
        this.oVector.remove(observer);
    }
    //通知所有观察者
    public void notifyObserver() {
        for(Observer observer : this.oVector) {
            observer.update();
        }
    }
}

2️⃣【抽象观察者 Observer】为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。它的每一个实现类都是具体观察者。

public interface Observer {
     //更新
     public void update();
}

3️⃣【具体主题 ConcreteSubject】将有关状态存入具体观察者对象,在具体主题内部状态改变时,给所有登记过的观察者发出通知。继承 Subject 类,在这里实现具体业务,在具体项目中,该类会有很多变种。

public class ConcreteSubject extends Subject {
    //具体业务
    public void doSomething() {
        //...
        super.notifyObserver();
    }
}

4️⃣【具体观察者 ConcreteObserver】实现抽象观察者 Observer 所要求的更新接口,以便使本身的状态与主题的状态相协同。

public class ConcreteObserver implements Observer {
    @Override
    public void update() {
        System.out.println("收到消息,进行处理");
    }
}

5️⃣【客户端 Client】首先创建一个主题,然后定义一个观察者,将该观察者添加到该主题的观察者数组中,进行测试。

public class Client {
    public static void main(String[] args) {
        //创建一个主题
        ConcreteSubject subject = new ConcreteSubject();
        //定义一个观察者
        Observer observer = new ConcreteObserver();
        //观察
        subject.addObserver(observer);
        //开始活动
        subject.doSomething();
    }
}

运行结果如下:

二、观察者模式的应用简例

假设上班时间有一部分同事在看股票,一部分同事在看 NBA,这时老板回来了,前台通知了部分同事,这些同事没被发现,而没被通知到的同事被抓现行,被老板亲自“通知”关闭网页,UML 图如下:

1️⃣主题 Subject通知的发布者

public interface Subject {
    //增加
    public void attach(Observer observer);
    //删除
    public void detach(Observer observer);
    //通知
    public void notifyObservers();
    //状态
    public void setAction(String action);
    public String getAction();
}

2️⃣观察者

public abstract class Observer {
    protected String name;
    protected Subject subject;
    public Observer(String name, Subject subject) {
        this.name = name;
        this.subject = subject;
    }
    public abstract void update();
}

3️⃣具体通知者
前台 Secretary 和老板 Boss 作为具体通知者,实现 Subject 接口。Secretary 类的代码如下:

public class Secretary implements Subject {
    //同事列表
    private List<Observer> observers = new LinkedList<>();
    private String action;
    //添加
    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }
    //删除
    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }
    //通知
    @Override
    public void notifyObservers() {
        for(Observer observer : observers) {
            observer.update();
        }
    }
    //前台状态
    @Override
    public String getAction() {
        return action;
    }
    @Override
    public void setAction(String action) {
        this.action = action;
    }
}

4️⃣具体观察者
StockObserver 是看股票的同事,NBAObserver 是看 NBA 的同事,作为具体观察者,继承 Observer 类。StockObserver 类的代码如下:

public class StockObserver extends Observer {
    public StockObserver(String name, Subject subject) {
        super(name, subject);
    }
    @Override
    public void update() {
        System.out.println(subject.getAction() + "\n" + name + "关闭股票行情,继续工作");
    }
}

5️⃣Client:前台作为通知者,通知观察者。这里添加 adam 和 tom 到通知列表,并从通知列表中删除了 adam,测试没在通知列表中的对象不会收到通知。

public class Client {
    public static void main(String[] args) {
        //前台为通知者
        Secretary secretary = new Secretary();
        StockObserver observer = new StockObserver("adam", secretary);
        NBAObserver observer2 = new NBAObserver("tom", secretary);

        //前台通知
        secretary.attach(observer);
        secretary.attach(observer2);

        //adam没被前台通知到,所以被老板抓了个现行
        secretary.detach(observer);

        //老板回来了
        secretary.setAction("小心!Boss回来了!");
        //发通知
        secretary.notifyObservers();
    }
}

运行结果如下,只有 tom 接收到了通知:

6️⃣Client:老板作为通知者,通知观察者。这里将 tom 从老板的通知列表中移除,老板只通知到了 adam。

public class Client {

    public static void main(String[] args) {
        //老板为通知者
        Boss boss = new Boss();

        StockObserver observer = new StockObserver("adam", boss);
        NBAObserver observer2 = new NBAObserver("tom", boss);

        //老板通知
        boss.attach(observer);
        boss.attach(observer2);

        //tom没被老板通知到,所以不用挨骂
        boss.detach(observer2);

        //老板回来了
        boss.setAction("咳咳,我大Boss回来了!");
        //发通知
        boss.notifyObservers();
    }
}

运行结果如下,只有 adam 挨骂了:

三、观察者模式的特性

1️⃣优点

  1. 一个对象状态改变,所有的依赖对象都将得到通知。
  2. 使用面向对象技术。
  3. 观察者和被观察者是抽象耦合的
  4. 建立了一套触发机制

2️⃣缺点

  1. 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间
  2. 如果观察者和观察目标间有循环依赖,可能导致系统崩溃
  3. 没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的

3️⃣使用场景

  1. 关联行为场景
  2. 事件多级触发场景
  3. 跨系统的消息变换场景,如消息队列的处理机制。

4️⃣应用实例

  1. 手机丢了,委托别人给其他人发消息通知。
  2. 通知老师/老板来了。
  3. 拍卖,拍卖师观察最高标价,然后通知给其它竞价者竞价。
  4. 在一个目录下建立一个文件,会同时通知目录管理器增加目录,并通知磁盘减少空间,文件是被观察者,目录管理器和磁盘管理器是观察者。
  5. 猫叫了一声,吓着了老鼠,也惊到了主人,猫是被观察者,老鼠和人是观察者。

5️⃣注意事项

  1. 避免循环引用
  2. 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
  3. 当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象有待改变的时候,应该考虑使用观察者模式。
  4. 将一个系统分割成一系列相互协作的类有一个很不好的副作用,就是需要维护相关对象间的一致性。为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便,而观察者模式所做的工作就是在解除耦合。
  • 4
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JFS_Study

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值