zoukankan      html  css  js  c++  java
  • 简说设计模式——观察者模式

    一、什么是观察者模式

      观察者一般可以看做是第三者,比如在学校上自习的时候,大家肯定都有过交头接耳、各种玩耍的经历,这时总会有一个“放风”的小伙伴,当老师即将出现时及时“通知”大家老师来了。再比如,拍卖会的时候,大家相互叫价,拍卖师会观察最高标价,然后通知给其它竞价者竞价,这就是一个观察者模式。

      对于观察者模式而言,肯定有观察者和被观察者之分。比如在一个目录下建立一个文件,这时系统会通知目录管理器增加目录,并通知磁盘减少空间,在这里,文件就是观察者,目录管理器和磁盘就是被观察者。

      观察者模式(Observer),又叫发布-订阅模式(Publish/Subscribe),定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。UML结构图如下:

       其中,Subject类是主题,它把所有对观察者对象的引用文件存在了一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供了一个接口,可以增加和删除观察者对象;Observer类是抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己;ConcreteSubject类是具体主题,将有关状态存入具体观察者对象,在具体主题内部状态改变时,给所有登记过的观察者发出通知;ConcreteObserver是具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协同。

      1. 主题Subject

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

    复制代码
     1 public class Subject {
     2 
     3     //观察者数组
     4     private Vector<Observer> oVector = new Vector<>();
     5     
     6     //增加一个观察者
     7     public void addObserver(Observer observer) {
     8         this.oVector.add(observer);
     9     }
    10     
    11     //删除一个观察者
    12     public void deleteObserver(Observer observer) {
    13         this.oVector.remove(observer);
    14     }
    15     
    16     //通知所有观察者
    17     public void notifyObserver() {
    18         for(Observer observer : this.oVector) {
    19             observer.update();
    20         }
    21     }
    22     
    23 }
    复制代码

      2. 抽象观察者Observer

      观察者一般是一个接口,每一个实现该接口的实现类都是具体观察者。

    1 public interface Observer {
    2     //更新
    3     public void update();
    4 }

       3. 具体主题

      继承Subject类,在这里实现具体业务,在具体项目中,该类会有很多变种。

    复制代码
    1 public class ConcreteSubject extends Subject {
    2 
    3     //具体业务
    4     public void doSomething() {
    5         //...
    6         super.notifyObserver();
    7     }
    8     
    9 }
    复制代码

       4. 具体观察者

      实现Observer接口。

    复制代码
    1 public class ConcreteObserver implements Observer {
    2 
    3     @Override
    4     public void update() {
    5         System.out.println("收到消息,进行处理");
    6     }
    7 
    8 }
    复制代码

       5. Client客户端

      首先创建一个被观察者,然后定义一个观察者,将该被观察者添加到该观察者的观察者数组中,进行测试。

    复制代码
     1 public class Client {
     2     
     3     public static void main(String[] args) {
     4         //创建一个主题
     5         ConcreteSubject subject = new ConcreteSubject();
     6         //定义一个观察者
     7         Observer observer = new ConcreteObserver();
     8         //观察
     9         subject.addObserver(observer);
    10         //开始活动
    11         subject.doSomething();
    12     }
    13     
    14 }
    复制代码

       运行结果如下:

      

    二、观察者模式的应用

      1. 何时使用

    • 一个对象状态改变,所有的依赖对象都将得到通知

       2. 方法

    • 使用面向对象技术

       3. 优点

    • 观察者和被观察者是抽象耦合的
    • 建立了一套触发机制

       4. 缺点

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

       5. 使用场景

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

      6. 应用实例

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

      7. 注意事项

    • 避免循环引用
    • 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式

    三、观察者模式的实现

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

      1. 通知者接口

    复制代码
     1 public interface Subject {
     2 
     3     //增加
     4     public void attach(Observer observer);
     5     //删除
     6     public void detach(Observer observer);
     7     //通知
     8     public void notifyObservers();
     9     
    10     //状态
    11     public void setAction(String action);
    12     public String getAction();
    13     
    14 }
    复制代码

       2. 观察者

    复制代码
     1 public abstract class Observer {
     2 
     3     protected String name;
     4     protected Subject subject;
     5     
     6     public Observer(String name, Subject subject) {
     7         this.name = name;
     8         this.subject = subject;
     9     }
    10     
    11     public abstract void update();
    12     
    13 }
    复制代码

      3. 具体通知者

      前台Secretary和老板Boss作为具体通知者,实现Subject接口。这里只给出Secretary类的代码,Boss类与之类似。

    复制代码
     1 public class Secretary implements Subject {
     2 
     3     //同事列表
     4     private List<Observer> observers = new LinkedList<>();
     5     private String action;
     6 
     7     //添加
     8     @Override
     9     public void attach(Observer observer) {
    10         observers.add(observer);
    11     }
    12 
    13     //删除
    14     @Override
    15     public void detach(Observer observer) {
    16         observers.remove(observer);
    17     }
    18 
    19     //通知
    20     @Override
    21     public void notifyObservers() {
    22         for(Observer observer : observers) {
    23             observer.update();
    24         }
    25     }
    26 
    27     //前台状态
    28     @Override
    29     public String getAction() {
    30         return action;
    31     }
    32 
    33     @Override
    34     public void setAction(String action) {
    35         this.action = action;
    36     }
    37     
    38 }
    复制代码

       4. 具体观察者

      StockObserver是看股票的同事,NBAObserver是看NBA的同事,作为具体观察者,继承Observer类。这里只给出StockObserver类的代码,NBAObserver类与之类似。

    复制代码
     1 public class StockObserver extends Observer {
     2     
     3     public StockObserver(String name, Subject subject) {
     4         super(name, subject);
     5     }
     6 
     7     @Override
     8     public void update() {
     9         System.out.println(subject.getAction() + "
    " + name + "关闭股票行情,继续工作");
    10     }
    11 
    12 }
    复制代码

       5. 前台作为通知者进行通知(Client)

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

    复制代码
     1 public class Client {
     2 
     3     public static void main(String[] args) {
     4         //前台为通知者
     5         Secretary secretary = new Secretary();
     6         
     7         StockObserver observer = new StockObserver("adam", secretary);
     8         NBAObserver observer2 = new NBAObserver("tom", secretary);
     9         
    10         //前台通知
    11         secretary.attach(observer);
    12         secretary.attach(observer2);
    13         
    14         //adam没被前台通知到,所以被老板抓了个现行
    15         secretary.detach(observer);
    16         
    17         //老板回来了
    18         secretary.setAction("小心!Boss回来了!");
    19         //发通知
    20         secretary.notifyObservers();
    21     }
    22     
    23 }
    复制代码

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

      

      6. 老板作为通知者进行通知(Client)

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

    复制代码
     1 public class Client {
     2 
     3     public static void main(String[] args) {
     4         //老板为通知者
     5         Boss boss = new Boss();
     6         
     7         StockObserver observer = new StockObserver("adam", boss);
     8         NBAObserver observer2 = new NBAObserver("tom", boss);
     9         
    10         //老板通知
    11         boss.attach(observer);
    12         boss.attach(observer2);
    13         
    14         //tom没被老板通知到,所以不用挨骂
    15         boss.detach(observer2);
    16         
    17         //老板回来了
    18         boss.setAction("咳咳,我大Boss回来了!");
    19         //发通知
    20         boss.notifyObservers();
    21     }
    22     
    23 }
    复制代码

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

      

      当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象有待改变的时候,应该考虑使用观察者模式。

      而使用观察者模式的动机在于:将一个系统分割成一系列相互协作的类有一个很不好的副作用,就是需要维护相关对象间的一致性,我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便,而观察者模式所做的工作就是在解除耦合

    https://gitee.com/adamjiangwh/GoF

  • 相关阅读:
    Leetcode———重建二叉树
    springboot+quartz实现定时任务发送邮件demo
    MySQL备份,使用xtrabackup备份全实例数据时,会造成锁等待吗?那么如果使用mysqldump进行备份呢?
    MySQL高可用架构应该考虑什么?你认为应该如何设计?
    你为什么会决定进行分库分表,分库分表过程中遇到什么难题,如何解决的?
    MySQL主从复制什么原因会造成不一致,如何预防及解决?
    用什么方法可以防止误删数据?
    MySQL每天产生了多大容量的binlog,用SQL语句能查到吗?
    你遇到过哪些原因造成MySQL异步复制延迟?
    为什么说 pt-osc 可能会引起主从延迟,有什么好办法解决或规避吗?
  • 原文地址:https://www.cnblogs.com/deepalley/p/14079594.html
Copyright © 2011-2022 走看看