【pattern】设计模式(3) - Observer观察者模式

源码地址:https://github.com/vergilyn/design-patterns
另外一个大神很全的Github:https://github.com/iluwatar/java-design-patterns

1. 概述

  观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当这个主题对象在状态发生变化时,可以通知所有观察者对象,使它们(观察者对象)能够自动更新自己。
  比如,微信用户就是观察者,微信公众号就是被观察者(主题对象),当公众号更新时会通知所有的观察者。
  以上示例更形象的表述可能是:发布/订阅(publish/subscribe)。
  (个人觉得)观察者模式的核心:其实就是触发及响应。所以感觉跟"责任链模式"有一定程度的类似,可能"责任链模式"的适用场景更加具体,而"观察者模式"相对就抽象很多。
  

2. 适用场景

  1. 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
    即一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。

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

  3. 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
    (事件总线)一个对象必须通知其他对象,而并不知道这些对象是谁;
    需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

  这些是我在网上乱七八糟看到的适用场景,能理解的很好理解,不能理解的很头疼。比如在看某些源码时,感觉像是observer-pattern,但又感觉不是...
  

3. 类图

涉及到角色:

  • AbstractSubject:抽象主题;
  • Subject:具体主题(被观察者);重点,会用一个集合List|Map|Set来保存所有的观察者。
  • AbstractObserver:抽象观察者;
  • Observer:具体观察者;

  注意:观察者observer与被观察者subject是相对的,某些情况下可能两者都是。

  之所以要定义AbstractSubject、 AbstractObserver,主要在于对开发原则(SOLID)的考虑,解除耦合及方便扩展。
  (看到有blog总结说Observer满足"开闭原则",我对SOLID理解不是那么深,只是有体会应该要依赖抽象/接口,所以才会定义AbstractSubject、 AbstractObserver)。
  特别:因为我定义的是interface AbstractSubject,所以我把observers定义到了Subject。视实际情况可能会定义为abstract class AbstractSubject,然后把observers定义在其中,所有AbstractSubject的子类共享observers.
  

4. 最基本的示例代码

  场景: subject[微信公众号],observer[微信用户]。当公众号更新时,将更新信息[message]通知所有的观察者。
  特别:observer-pattern可以分为"推模型"、"拉模型",示例代码是"推模型"。

/** 抽象主题 */
public interface AbstractSubject {
    String name();
    void add(AbstractObserver observer);
    void del(AbstractObserver observer);
    void publishMessage(String message);
}
/** 抽象观察者 */
public interface AbstractObserver {
    String name();
    void onMessage(AbstractSubject subject, String msg);
}
/** 具体主题(被观察者)*/
public class Subject implements AbstractSubject {
    private final Set<AbstractObserver> observers = Sets.newLinkedHashSet();
    private final String name;

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

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

    @Override
    public void add(AbstractObserver observer) {
        observers.add(observer);
    }

    @Override
    public void del(AbstractObserver observer) {
        observers.remove(observer);
    }

    @Override
    public void publishMessage(String message) {
        for (AbstractObserver observer : observers){
            observer.onMessage(this, message);
        }
    }

}
/** 具体观察者 */
public class Observer implements AbstractObserver {
    private final String name;

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

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

    public void onMessage(AbstractSubject subject, String msg) {
        System.out.println(String.format("observer[%s]: subject[%s], message[%s]", name, subject.name(), msg));
    }
}
public class TestObserver {
    public static void main(String[] args) {
        AbstractSubject subject = new Subject("公众号-01");

        AbstractObserver observer1 = new Observer("user-01");
        AbstractObserver observer2 = new Observer("user-02");
        AbstractObserver observer3 = new Observer("user-03");

        subject.add(observer1);
        subject.add(observer2);
        subject.add(observer3);

        subject.publishMessage("some message");
    }
}
输出结果:
observer[user-01]: subject[公众号-01], message[some message]
observer[user-02]: subject[公众号-01], message[some message]
observer[user-03]: subject[公众号-01], message[some message]

  

5. 注意事项

  1. (开发效率)虽然observer-pattern代码看起来很简单,但如果用于实际业务逻辑代码,非框架级代码,用observer-pattern会让开发、调试、阅读等内容变得比较复杂。

  2. (运行效率)如果一个subject有很多直接和间接的observer的话,将所有的observers都通知到会花费很多时间。
    解决:
      视具体情况,更多可能采用异步的方式通知各个observer。避免observer之间相互的影响。
      且要注意若通知某个observer发生exception时,subject要怎么处理?是由subject处理还是由observer处理?

  3. (循环依赖)如果在observer和subject之间有循环依赖的话,注意避免循环调用,否则会导致系统崩溃。

  4. (缺点)observer-pattern没有相应的机制让observer知道subject是怎么发生变化的,而仅仅只是知道subject发生了变化。
      个人并不理解,感觉有办法能让object知道subject是怎么发生变化的啊。

  5. 抉择:是用"推模式",还是"拉模式"。

6. "推模型"、"拉模型"

个人认同的"推模型"、"拉模型"说法:

  • "推模型":subject维护一份observers的列表,每当有更新发生,subject会把需要的参数主动推送到各个observer。

  • "拉模型": observer维护自己所关心的subjects列表,自行决定在合适的时间(延时性)去subjects获取相应的更新数据。

  我最初也没扯清楚"推模型"、"拉模型"的区别。然后去看了几篇blog,发现有两种说法,有种说法跟我最初理解的一样,但当我看见另外一种说法时,我发现更能接受"另外"的这种说法。
  "推模型"、"拉模型"的区别是:如果subject是主动的一方,则是"推模型";反之,若observer是主动方,则是"拉模型"。
  "推模型",如上面代码示例,是由subject维护了一份observers,由subject主动把消息推送给observer。
  而如果是"拉模型",则是反过来,由observer维护一份subjects,并且是有observer定时去获取subject的信息。
  
(两种说法的参考blog)  

  1. 个人认为:错误的理解
    参考:http://blog.csdn.net/happyever2012/article/details/44678595
      这边文章中所的"推"、"拉"跟我最初理解的一样, 但我一直感觉他们实质都是一样的。
    当subject通知observer时,不管传需求的部分参数,还是把subject本身传递给observer,感觉都像"推"。所以,我就继续在网上找别的blog,找到了我认同的说法。

  2. 个人认为:正确的理解
    参考:http://raychase.iteye.com/blog/1337015

6.1. "推模型"、"拉模型"的各自特点

"推模型"的特点:
  1) 精准。如果没有更新发生,不会有任何更新消息推送的动作,即每次消息推送都发生在确确实实的更新事件之后。
  2) 实时。事件发生后的第一时间即可触发通知操作。
  3) 可以表达出不同事件发生的先后顺序。保证了是按事件改变顺序通知observer。
  4) 因为发起方是subject,所以要在subject方指定通知的时间,避开subject自身或observer方的繁忙时段。

"拉模型"的特点:
  1) 延时性。因为是定时去请求subject的信息,所以有延时性。
  2) (不知道怎么描述)同样因为定时,如果长时间subject没变化,observer还是会按时去更新subject,做是否更新判断,无意义的请求获取subject。
  3) 因为发起方是observer,所以要在observer方指定更新的时间,避开observer自身或subject方的繁忙时段。
  
  这里先要明白一个概念,subject一般被认为是服务端,observer则是客户端。所以有些可能subject与observer根本不是同一个服务(不在同一套代码内)。
  所以,如果需由observer自己控制自己的更新时间,那可以考虑选择"拉模型"。反之,若subject觉得这时间段subject服务器压力太大,又不是一定要马上通知各个observer,则"推模式"更容易在subject控制更新。

6.2. "推模型"、"拉模型"注意事项

  1) 均无法保证事件都会响应
    "推模型"中,若当subject通知observer时,某个observer客户端已经崩溃,那么要考虑subject服务端是不是要记录这次失败,重新通知这个observer直到成功。
    "拉模型"中,因为延时性,subject快速的状态改变1 -> 2 -> 3,observer定时获取的值是3,那么要考虑observer是不是要获取完整的状态变化过程执行相应的逻辑代码。
    
  2) "权限控制"交给谁?
    (这可能无关"推模型"、"拉模型") 比如,"推模式"subject会把所有的触发事件都通知给全部observers,至于这个事件各个observer是不是需要处理,各个observer自己去考虑。(当然,代码完全可以写成是在服务端subject控制某个事件不通知某些observer)。
    可能类似的场景如,subject服务端提供了很多免费的服务,observer客户端要用哪些自己考虑。subject服务端提供了一些收费服务,此时不管是"推模型"还是"拉模型"肯定都只能在subject服务端控制这个权限。

  3) 压力交给谁去承受?
    比如,若observers数据量相当庞大,subject维护这份数据压力很大(或全部通知到压力很大),则可以把关系解脱到observer去完成,即选择"拉模型"。
    又或者,subject服务端觉得被一堆observer定时请求压力较大,则选择"推模型"。
    
  以上很多都可能是我多此一举的瞎考虑, 其实这点性能差别根本不用考虑。
  暂时感觉比较明显的一点,是不是要保证实时性。
  实际中我看的较多都是"推模型","拉模型"少很多。比较明显的"拉模型"就是互联网页面的访问,

7. 扩展: 观察者模式 vs 责任链模式

  暂时还没有仔细看"责任链模式",而且只是个人感觉"责任链模式"和"观察者模式"很相似,都是基于"因为某个东西被触发,所以需要执行操作"。只是"责任链模式"更具体,"观察者模式"的使用场景更抽象。
参考:
  1. https://www.cnblogs.com/cbf4life/archive/2009/12/29/1634773.html
    以上blog并没有让我明白他们之间的区别。
  2. http://bbs.csdn.net/topics/330020444
    可以参考,最后提到的"推模型"与"拉模型"比较有意义。但其余的回答我还是没理解。

8. 实际场景: Redisson源码中的observer-pattern(publish/subscribe)

  场景简述:redisson是用redis实现分布式并发锁,当多个线程竞争同一把锁时,只会有一个线程会获得锁,此时其余线程都在等待获取锁。当持有锁线程执行完并释放锁时,需要通知其余线程去竞争获取锁。此时就是可以用publish/subscribe。
  线程A先去尝试获取锁,发现已被其它线程X持有,此时线程A就subscribe竞争该锁的subject。当线程X释放锁时,及时publish通知所有的observer去竞争锁。(实际redisson中并没有通知全部的observer,而是只通知队列中的第一个竞争线程,让其获得锁。)
  
  本来打算把redisson的相关源码贴出来,但略多,而且要解释的太多了。等写redisson源码阅读再说吧。

posted @ 2018-03-04 00:33  淡丶无欲  阅读(357)  评论(0编辑  收藏  举报