生活中的设计模式之观察者模式

定义

defines a one to many dependency between objects so that one object changes state, all of its dependents are notified and updated automatically.

定义对象之间的一对多依赖关系,这样一旦对象改变状态,所有它的依赖对象都会自动收到通知或更新。

实列

生活中,有些现象和观察者模式非常的相似。
比如说,在网购商品时,其中某些商品的价格非常低廉而且你也非常心动,但是它目前处于缺货状态,什么时候补货商家自己也不知道。
此时,商家为了促成交易,会在商品的购买按钮旁边放置一个"到货通知"按钮,如果你选择了到货通知,那么一旦该商品补上库存,那么你就会立即收到订阅通知。
在这个过程中你便是订阅者或者说观察者,但你不是唯一的订阅着,还有其它和你一样的订阅者也都会收到通知,而商家便是发布通知的发布者;如果你不订阅,那么你得不停的登录网站查看商品的到货情况。
如果用观察者模式的话说,商品便是主题,你便是订阅者,一个主题有多个订阅者。

故事

两年前,我入职了一家电商公司并负责订单模块的研发工作。刚开始,订单模块的需求特别简单,只需在订单状态从待支付变成已支付时,通知一下库存模块和物流模块进行扣减库存和发货操作;
后来,很多新模块如用户积分、优惠券、财务的新增都需要在订单状态变化时通知它们,没办法我就只能不停的修改订单类,最后在我离职时订单类的代码已经惨目忍睹了。如下:


public class Order {

    protected String status;

    public void payed() {

        this.status = "支付成功";
        //扣减库存
        Inventory inventory = new Inventory();
        inventory.decrease();

        //发货
        Delivery delivery = new Delivery();
        delivery.deliver();

        //增加用户积分
        UserPoint userPoint = new UserPoint();
        userPoint.increase();
        //......
    }

}

问题

故事中,订单有一个状态,这个状态会发生变化,一旦状态改变这一事件发生,为了让库存,物流,积分等关注者知晓该事件,它会分别调用这些对象的具体方法。
关注者们分属不同模块,它们类型各异而且数量也是不固定的,这就导致发布者不能以一致的方式调用它们,新增关注者需要修改代码,也不能运行时动态移除关注者。
那事件的发布者订单真的需要耦合这些关注者吗?直接调用它们的方法只是为了通知他们,其实可以以一致的方式调用的,因为入参是相同的,也不依赖返回值,仅仅是方法名不同。
所以,有没有一种方式可以解决一致、扩展,动态的问题,这便是观察者模式。

方案

在观察者模式中,关注主题状态变化的对象被称为订阅者或观察者,它们会实现一个统一的订阅接口,这解决了一致的问题;
订单对象被称之为发布者或主题,它维护着一个订阅者列表但不依赖具体订阅者,并对外提供注册和注销成为订阅者的操作,这解决了动态的问题,
当一致和动态问题解决了之后,新增订阅者只需实现订阅接口以及注册成为订阅者,这就解决了扩展的问题。

这样,发布者发生状态变化时,它一般会将变化的数据包装成一个事件对象,然后遍历订阅者列表将事件通知到每一个订阅者。

应用

接下来,我们使用观察者模式重构一下订单模块。

首先,声明一个统一的订阅者角色接口,它包含一个接收通知的方法。


/**订单订阅者接口*/
public interface OrderSubscriber {

    public void notify(Event event);
}

然后,让关注事件的对象都实现该接口。


/**库存订阅者*/
public class Inventory implements OrderSubscriber{
    public void decrease(){
        System.out.println("库存扣减");
    }
    @Override
    public void notify(Event event) {
        System.out.println("收到事件后,触发库存扣减");
        decrease();

    }
}

/**物流订阅者*/
public class Delivery implements OrderSubscriber{
    public void deliver(){
        System.out.println("发货");
    }
    @Override
    public void notify(Event event) {
        System.out.println("收到事件后,触发发货操作");
        deliver();

    }
}

现在,我们修改一下订单类Order,让它对外提供一个注册以及注销订阅者身份的方法,这样可以在运行时动态添加或移除订阅者。




public class Order {

    protected String status;

    protected List orderObservers = new LinkedList<>();

    public void register(OrderSubscriber observer){
        orderObservers.add(observer);
    }

    public void unRegister(OrderSubscriber observer){
        orderObservers.remove(observer);
    }

    public void payed() {

        this.status = "支付成功";
        //通知订阅者
        for (OrderSubscriber orderObserver : orderObservers) {
            //将变化的状态信息封装成事件
            Event event = new Event(status);
            orderObserver.notify(event);
        }
    }

}


最后,我们在看看如何使用订阅者模式。

public class Client {
    public static void main(String[] args) {
        //发布者
        Order order = new Order();
        //订单事件的订阅者
        OrderSubscriber inventory = new Inventory();
        OrderSubscriber delivery = new Delivery();

        //注册订阅者
        order.register(inventory);
        order.register(delivery);

        //触发订单更新
        order.payed();
    }
}

结构

avatar

抽象订阅者角色(Subscribe) :它声明了一个通知接口,该接口通常包含一个以事件(Event)为入参的方法,事件中封装了主题状态变化的数据。

具体订阅者角色(ConcreteSubscribe):它是事件的订阅者会被注册到主题的订阅者列表中,负责接收并处理来自发送者的事件。

发布者角色(Publisher) :它既是主题也是事件的发布者,它有一个会发生变化的状态和一个指向订阅者的引用列表,当状态发生变化时,变化数据会被封装成一个事件并发送给列表中的每一个订阅者。


/**抽象订阅者*/
public interface Subscriber {

    public void notify(Event event);
}

/**具体订阅者A*/
public class ConcreteSubscribeA implements Subscriber{

    protected void doSomeThing(){}

    @Override
    public void notify(Event event) {
        doSomeThing();
    }
}

/**具体订阅者B*/
public class ConcreteSubscribeA implements Subscriber{

    protected void doSomeThing(){}

    @Override
    public void notify(Event event) {
        doSomeThing();
    }
}

/**发布者*/
public class Publisher {
    /**会变化的状态*/
    protected String status;
    /**订阅者列表*/
    protected List subscribers = new LinkedList<>();
     /**注册*/
    public void register(Subscriber observer){
        subscribers.add(observer);
    }
    
    /**注销*/
    public void unRegister(Subscriber observer){
        subscribers.remove(observer);
    }

     /**改变状态*/
    public void changeStatus(){
        this.status="changeStatus";
        for (Subscriber subscriber : subscribers) {
            Event event = new Event(status);
            subscriber.notify(event);
        }
    }
}

public class Client {
    public static void main(String[] args) {
        Publisher publisher = new Publisher();
        ConcreteSubscribeA subscribeA = new ConcreteSubscribeA();
        ConcreteSubscribeB subscribeB = new ConcreteSubscribeB();
        //注册订阅者
        publisher.register(subscribeA);
        publisher.register(subscribeB);
        //触发状态变化并通知订阅者
        publisher.changeStatus();

    }
}

总结

当一个事件存在多个不同类型的订阅者时,为了避免发布者和订阅者耦合,首先为事件的通知定义一个同一接口,然后让发布者提供一种机制允许订阅者关注和取关事件。
这样,可以使发布者以一致的方式将事件发送给订阅者,也能运行时动态注册和注销订阅者,还能在不修改发布类的前提下扩展订阅者。

你可能感兴趣的:(生活中的设计模式之观察者模式)