首页 > 代码库 > 观察者模式

观察者模式

观察者模式

标签 : Java与设计模式


观察者模式: 又称‘发布-订阅’模式, 定义一种对象间的一对多依赖关系(多个观察者Observer监听某一主题Subject). 当主题状态发生改变时,所有依赖它的对象都得到通知并被自动更新.
技术分享
核心: 触发联动(图片来源: 设计模式: 可复用面向对象软件的基础)


模式实现

以电商系统下单: 
用户购买某件商品下一个订单, 需要: 通知库存系统减少库存、通知商家系统发货、通知支付系统收钱、甚至还会通知关系中心使当前用户关注该商家.

技术分享


Subject

目标/主题/抽象通知者:

  • Subject知道它所有的观察者, 可以有任意多个观察者监听同一个目标(将观察者保存在一个聚集中);
  • 提供注册/删除观察者的接口.
/**
 * @author jifang
 * @since 16/8/30 上午9:49.
 */
public abstract class Subject {

    protected List<Observer> observers = new LinkedList<>();

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void detach(Observer observer) {
        observers.remove(observer);
    }

    protected abstract void notifyObservers();

    public abstract String getState();

    public abstract void setState(String state);
}

ConcreteSubject

具体目标/具体主题:

  • 将有关状态存入ConcreteSubject;
  • 当它的状态发生改变时, 向各个观察者发出通知.
class OrderSubject extends Subject {

    private String state;

    /**
     * 采用拉模型, 将Subject自身发送给Observer
     */
    @Override
    protected void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(this);
        }
    }

    @Override
    public String getState() {
        return state;
    }

    @Override
    public void setState(String state) {
        this.state = state;
        this.notifyObservers();
    }
}

Observer

抽象观察者: 为那些在目标状态发生改变时需获得通知的对象定义一个更新接口.

public interface Observer {

    void update(Subject subject);
}

ConcreteObserver

具体观察者:

  • 存储有关状态, 这些状态应与目标的状态保持一致;
  • 实现Observer的更新接口以使自身状态与目标的状态保持一致;
  • _维护一个指向ConcreteSubject对象的引用.
class WareHouseObserver implements Observer {

    private String orderState;

    @Override
    public void update(Subject subject) {
        orderState = subject.getState();
        System.out.println("库存系统接收到消息 [" + orderState + "], 减少库存");
    }
}

class PayObserver implements Observer {

    private String orderState;

    @Override
    public void update(Subject subject) {
        orderState = subject.getState();
        System.out.println("支付系统接收到消息 [" + orderState + "], 正在收钱");
    }
}

class RelationObserver implements Observer {

    private String orderState;

    @Override
    public void update(Subject subject) {
        orderState = subject.getState();
        if (orderState.equals("已付款")) {
            System.out.println("关系系统接收到消息 [" + orderState + "], 当前用户已关注该店铺");
        } else if (orderState.equals("取消订单")) {
            System.out.println("关系系统接收到消息 [" + orderState + "], 当前用户取消关注该店铺");
        }
    }
}
  • Client
public class Client {

    @Test
    public void client() {
        Subject subject = new OrderSubject();

        Observer payObserver = new PayObserver();
        Observer relationObserver = new RelationObserver();
        Observer wareHouseObserver = new WareHouseObserver();

        // 注册到Subject
        subject.attach(payObserver);
        subject.attach(relationObserver);
        subject.attach(wareHouseObserver);

        subject.setState("已付款");

        System.out.println("-------------");
        // 付钱、发货完成
        subject.detach(payObserver);
        subject.detach(wareHouseObserver);

        subject.setState("取消订单");
    }
}

通知方式

  • 以上我们采用的是拉模型实现SubjectObserver通知(传递Subject自身), 在观察者模式中还有一种推模型实现:

    • 拉模型
      Subject把自身(this)通过update()方法传递给观察者, 观察者只要知道有通知到来即可, 至于什么时候获取什么内容都可自主决定.
    • 推模型
      Subject主动向观察者推送有关状态的详细信息, 推送的信息通常是目标对象的全部或部分数据. 观察者只能被动接收.
  • 对比
    推模型中假定Subject知道观察者需要数据的详细信息, 而拉模型中Subject不需要知道观察者具体需要什么数据(因此把自身传过去, 由观察者取值).因此:

    • 推模型会使观察者对象难以复用;
    • 拉模型下, 由于update()方法参数是Subject本身, 基本上可以适应各种情况的需要.

JDK支持

Java语言自身提供了对观察者模式的支持: java.util包下提供了Observable类与Observer接口. 下面我们就用Java的支持实现观察者模式的推模型:

  • ConcreateSubject
public class OrderSubject extends Observable {

    private String state;

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
        this.setChanged();
        this.notifyObservers(state);
    }

    @Override
    public String toString() {
        String result = "OrderSubject{";
        try {
            Field obs = Observable.class.getDeclaredField("obs");
            obs.setAccessible(true);
            Vector vector = (Vector) obs.get(this);
            result += vector;
        } catch (NoSuchFieldException | IllegalAccessException ignored) {
        }
        return result +
                "state=‘" + state + ‘\‘‘ +
                ‘}‘;
    }
}
  • ConcreteObserver
public class WareHouseObserver implements Observer {

    private String orderState;

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("拉模式: " + o);
        orderState = (String) arg;
        System.out.println("推模式: 库存系统接收到消息 [" + orderState + "], 减少库存");
    }
}

Guava支持

Guava提供EventBus以取代发布者订阅者之间的显式注册, 取而代之的是使用注解@Subscribe, 使组件间有更好的解耦.


Event

封装消息类
EventBusEvent继承: EventBus自动把事件分发给事件超类的监听者/观察者,并允许监听者声明监听接口类型泛型的通配符类型(如 ? super Xxx).

interface Event {
    String getState();
}
  • 注:
    • DeadEvent : 如果EventBus发送的消息都不是订阅者关心的称之为DeadEvent.
    • 每个用@Subscribe注解标注的方法只能有一个参数.

Subject

使用Guava之后, 如果要订阅消息, 就不用再实现指定的接口, 只需在指定的方法上加上@Subscribe注解即可, 但为了代码的易读性, 我们还是推荐保留公共的接口:

public interface Observer {
    void update(Event event);
}

Producer

  • 管理Listener/Observer: EventBus内部已经实现了的观察者/监听者管理;
  • 分发事件: 将事件传递给EventBus.post(Object)方法即可, 异步分发可以直接用EventBus子类AsyncEventBus.
public class Producer {

    private static final Logger LOGGER = Logger.getLogger(Client.class.getName());

    @Test
    public void client() {
        EventBus bus = new EventBus("observer-pattern");
        bus.register(new Observer() {

            @Subscribe
            @Override
            public void update(Event event) {
                System.out.println("库存系统接收到消息 [" + event.getState() + "], 减少库存");
            }
        });
        bus.register(new Observer() {

            @Subscribe
            @Override
            public void update(Event event) {
                System.out.println("支付系统接收到消息 [" + event.getState() + "], 正在收钱");
            }
        });

        // 不用实现接口, 直接给出一个Object对象也可
        bus.register(new Object() {

            @Subscribe
            public void onEvent(Event event) {
                System.out.println("关系系统接收到消息 [" + event.getState() + "], 当前用户关注店铺");
            }

            @Subscribe
            public void onEventFun(Event event) {
                System.out.println("我就是来打酱油的o(╯□╰)o");
            }
        });

        // 注册DeadEvent
        bus.register(new Object() {
            @Subscribe
            public void onDead(DeadEvent dead) {
                LOGGER.log(Level.WARNING, "没有消费者接收" + dead);
            }
        });

        // 发布消息
        bus.post(new Event() {
            @Override
            public String getState() {
                return "付钱成功";
            }
        });

        bus.post("dead event O(∩_∩)O~");
    }
}

注: 线程间通信框架Disruptor也是观察者模式的一种具体实现, 详细可参考博客: Java并发基础:Disruptor小结、并发框架Disruptor译文.


小结

将系统分割成一系列相互协作的类有一定的副作用: 需要维护相关对象间的一致性, 我们不希望为了一致性而将各类紧密耦合, 这样会给维护、扩展和重用都带来不便.
而观察者模式允许独立的改变目标和观察者. 你可以单独复用Subject而不用管Observer 反之亦然. 它也使你可以在不改动Subject和其他Observer的前提下增加观察者.

  • 场景:
    当一个抽象模型有两个方面, 其中一方依赖于另一方(一方改变需要通知另一方, 且它不知道具体有多少对象等待待改变), 这时观察者就可将这两者封装在独立的对象中使他们各自独立的改变和复用
    • 关注微信公众号 & 邮件订阅;
    • 网络游戏中服务器将客户状态转发;
    • Servlet API: 监听器Listener;
    • Android广播机制;
    • AWT事件处理模型(基于观察者模式的委派事件模型).

参考:
设计模式: 可复用面向对象软件的基础
大话设计模式
慕课: 观察者模式
高淇讲设计模式
设计模式有害论
Guava学习笔记: EventBus
Guava官方文档-事件总线

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    观察者模式