观察者模式(Observer Pattern)

Posted 顧棟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了观察者模式(Observer Pattern)相关的知识,希望对你有一定的参考价值。

观察者模式(Observer Pattern)

观察者模式的定义

定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

观察者模式的优点

  1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
  2. 目标与观察者之间建立了一套触发机制。

观察者模式的缺点

  1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  2. 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
  3. 多级触发时的效率更是让人担忧

观察者模式的结构

观察者模式的主要角色如下。

  1. 抽象主题(Subject)角色:也叫抽象目标类,被观察者,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  2. 具体主题(Concrete Subject)角色:具体的被观察者,也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  3. 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  4. 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

观察者模式的实现

实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。

通用实现

/**
 * 抽象被观察者
 */
public abstract class Subject {
    /**
     * 定义一个观察者数组
     */
    private final Vector<Observer> obsVector = new Vector<>();

    /**
     * 增加一个观察者
     */
    public void addObserver(Observer o) {
        this.obsVector.add(o);
    }

    /**
     * 删除一个观察者
     */
    public void delObserver(Observer o) {
        this.obsVector.remove(o);
    }

    /**
     * 通知所有观察者
     */
    public void notifyObservers() {
        for (Observer o : this.obsVector) {
            o.update();
        }
    }
}
/**
 * 观察者
 */
public interface Observer {
    /**
     * 更新方法
     */
    public void update();
}
/**
 * 具体被观察者
 */
public class ConcreteSubject extends Subject {
    /**
     * 具体的业务
     */
    public void doSomething() {
        /*
         * do something
         */
        super.notifyObservers();
    }
}
/**
 * 具体观察者
 */
public class ConcreteObserver implements Observer {
    /**
     * 更新方法
     */
    @Override
    public void update() {
        System.out.println("接收到信息,并进行处理!");
    }
}
public static void main(String[] args) {
    //创建一个被观察者
    ConcreteSubject subject = new ConcreteSubject();
    //定义一个观察者
    Observer obs= new ConcreteObserver();
    //观察者观察被观察者
    subject.addObserver(obs);
    //观察者开始活动了
    subject.doSomething();
}

小客车摇号通知场景

/**
 * 小客车指标调控服务
 */
public class MinibusTargetService {
    /**
     * 模拟摇号,但不是摇号算法
     *
     * @param uId 用户编号
     * @return 结果
     */
    public String lottery(String uId) {
        return Math.abs(uId.hashCode()) % 2 == 0 ? "恭喜你,编码".concat(uId).concat("在本次摇号中签") : "很遗憾,编码".concat(uId).concat("在本次摇号未中签或摇号资格已过期");
    }
}
public class LotteryResult {
    /**
     * 用户ID
     */
    private String uId;
    /**
     * 摇号信息
     */
    private String msg;
    /**
     * 业务时间
     */
    private Date dateTime;

    public LotteryResult(String uId, String msg, Date dateTime) {
        this.uId = uId;
        this.msg = msg;
        this.dateTime = dateTime;
    }

    public String getuId() {
        return uId;
    }

    public void setuId(String uId) {
        this.uId = uId;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Date getDateTime() {
        return dateTime;
    }

    public void setDateTime(Date dateTime) {
        this.dateTime = dateTime;
    }
}
/**
 * 抽象被观察者--摇号服务抽象类
 */
public abstract class LotteryService {
    private EventManager eventManager;

    public LotteryService() {
        eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);
        eventManager.subscribe(EventManager.EventType.MQ, new MQEventListener());
        eventManager.subscribe(EventManager.EventType.Message, new MessageEventListener());
    }

    public LotteryResult draw(String uId) {
        LotteryResult lotteryResult = doDraw(uId);
        // 需要什么通知就给调用什么方法
        eventManager.notify(EventManager.EventType.MQ, lotteryResult);
        eventManager.notify(EventManager.EventType.Message, lotteryResult);
        return lotteryResult;
    }
    
    protected abstract LotteryResult doDraw(String uId);
}
/**
 * 具体被观察者--摇号服务类
 */
public class LotteryServiceImpl extends LotteryService {

    private final MinibusTargetService minibusTargetService = new MinibusTargetService();

    @Override
    protected LotteryResult doDraw(String uId) {
        // 摇号
        String lottery = this.minibusTargetService.lottery(uId);
        // 结果
        return new LotteryResult(uId, lottery, new Date());
    }
}
/**
 * 抽象观察者
 */
public interface EventListener {
    void doEvent(LotteryResult result);
}
/**
 * 具体观察者-- 短消息监听器
 */
public class MessageEventListener implements EventListener {
    @Override
    public void doEvent(LotteryResult result) {
        System.out.println("给用户 " + result.getuId() + " 发送短信通知(短信):" + result.getMsg());
    }
}
/**
 * 具体观察者-- MQ消息监听器
 */
public class MQEventListener implements EventListener {
    @Override
    public void doEvent(LotteryResult result) {
        System.out.println("记录用户 "+result.getuId()+" 摇号结果(MQ):"+result.getMsg());
    }
}
/**
 * 观察者处理类--事件处理类
 */
public class EventManager {
    Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>();

    public EventManager(Enum<EventType>... operations) {
        for (Enum<EventType> operation : operations) {
            this.listeners.put(operation, new ArrayList<>());
        }
    }

    public enum EventType {
        MQ,
        Message
    }

    /**
     * 订阅
     *
     * @param eventType 事件类型
     * @param listener  监听
     */
    public void subscribe(Enum<EventType> eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.add(listener);
    }

    /**
     * 取消订阅
     *
     * @param eventType 事件类型
     * @param listener  监听
     */
    public void unsubscribe(Enum<EventType> eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.remove(listener);
    }

    /**
     * 通知
     *
     * @param eventType 事件类型
     * @param result    结果
     */
    public void notify(Enum<EventType> eventType, LotteryResult result) {
        List<EventListener> users = listeners.get(eventType);
        for (EventListener listener : users) {
            listener.doEvent(result);
        }
    }
}
public static void main(String[] args) {
    LotteryService lotteryService = new LotteryServiceImpl();
    LotteryResult result = lotteryService.draw("2765789109876");
    System.out.println("测试结果:" + JSON.toJSONString(result));
}

观察者模式的适用场景

  1. 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
  2. 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  3. 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
  4. 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。
  5. 关联行为场景。需要注意的是,关联行为是可拆分的,而不是"组合"关系。
  6. 事件多级触发场景。
  7. 跨系统的消息交换场景,如消息队列的处理机制。

注意事项

  • 广播链的问题

一个观察者可以有双重身份,既是观察者,也是被观察者,这没什么问题呀,但是链一旦建立,这个逻辑就比较复杂,可维护性非常差,根据经验建议,在一个观察者模式中最多出现一个对象既是观察者也是被观察者,也就是说消息最多转发一次(传递两次),这还是比较好控制的。

  • 异步处理问题

被观察者发生动作了,观察者要做出回应,如果观察者比较多,而且处理时间比较长怎么办?那就用异步呗,异步处理就要考虑线程安全和队列的问题。


本文主要参考:

  1. 小傅哥的《重学Java模式》
  2. 《C语言中文网》设计模式的相关内容
  3. 《设计模式之禅》第二版 秦小波

以上是关于观察者模式(Observer Pattern)的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之- 观察者模式(Observer Pattern)

观察者模式(Observer Pattern)

秒懂设计模式之观察者模式(Observer Pattern)

秒懂设计模式之观察者模式(Observer Pattern)

观察者模式(Observer Pattern)

[Design Pattern] Observer Pattern 简单案例