设计模式之观察者模式

定义

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。如微博中一个明星发了一个消息,所有关注的用户都会看到这条消息。

结构

  • Observer,观察者接口,定义目标通知时对应的更新方法。
  • ConcreteObserver,具体观察者,接收目标通知,并进行相应的业务处理。
  • Subject,目标对象,提供观察者注册和退订的方法,当目标状态发生变化时,通知所有已注册的观察者。
  • ConcreteSubject,具体的目标对象,维护目标状态,通知所有已注册的观察者。

简单实现

观察者接口

/**
 * 观察者接口
 */
public interface Observer {

  void update(Subject subject);

}

具体观察者

/**
 * 观察者实现
 */
public class ConcreteObserver implements Observer {

  @Override
  public void update(Subject subject) {
    ConcreteSubject concreteSubject = (ConcreteSubject) subject;
    System.out.println("ConcreteObserver," + concreteSubject.getSubjectState());
  }
}

另一个观察者

/**
 * 观察者实现
 */
public class ConcreteObserver2 implements Observer {

  @Override
  public void update(Subject subject) {
    ConcreteSubject concreteSubject = (ConcreteSubject) subject;
    System.out.println("ConcreteObserver2," + concreteSubject.getSubjectState());
  }
}

目标对象

import java.util.ArrayList;
import java.util.List;

public class Subject {

  private List<Observer> observers = new ArrayList<>();

  /**
   * 注册观察者
   */
  public void attach(Observer observer) {
    observers.add(observer);
  }

  /**
   * 删除观察者
   */
  public void detach(Observer observer) {
    observers.remove(observer);
  }

  /**
   * 通知所有观察者
   */
  protected void notifyObservers() {
    for (Observer observer : observers) {
      observer.update(this);
    }
  }

}

具体的目标对象

public class ConcreteSubject extends Subject {

  private String subjectState;

  public String getSubjectState() {
    return subjectState;
  }

  public void setSubjectState(String subjectState) {
    this.subjectState = subjectState;
    notifyObservers();
  }
}

客户端

public class Client {

  public static void main(String[] args) {
    ConcreteSubject subject = new ConcreteSubject();
    subject.attach(new ConcreteObserver());
    subject.attach(new ConcreteObserver2());
    subject.setSubjectState("test");
  }

}

其实java中已经提供了 Observer 接口和 Observable 目标类,我们只需要在此基础上实现就可以了。

import java.util.Observable;
import java.util.Observer;

/**
 * 观察者实现
 */
public class ConcreteObserver implements Observer {

  @Override
  public void update(Observable o, Object arg) {
    ConcreteSubject concreteSubject = (ConcreteSubject) o;
    System.out.println("ConcreteObserver," + concreteSubject.getSubjectState());
  }
}
import java.util.Observable;
import java.util.Observer;

/**
 * 观察者实现
 */
public class ConcreteObserver2 implements Observer {

  @Override
  public void update(Observable o, Object arg) {
    ConcreteSubject concreteSubject = (ConcreteSubject) o;
    System.out.println("ConcreteObserver2," + concreteSubject.getSubjectState());
  }
}

具体的目标对象

import java.util.Observable;

public class ConcreteSubject extends Observable {

  private String subjectState;

  public String getSubjectState() {
    return subjectState;
  }

  public void setSubjectState(String subjectState) {
    this.subjectState = subjectState;
    setChanged();
    notifyObservers();
  }
}

客户端

public class Client {

  public static void main(String[] args) {
    ConcreteSubject subject = new ConcreteSubject();
    subject.addObserver(new ConcreteObserver());
    subject.addObserver(new ConcreteObserver2());
    subject.setSubjectState("test");
  }

}

推模型和拉模型

观察者模式有两种实现方式,推模型和拉模型。

  • 推模型
    目标对象主动向观察者推送目标的详细信息,不管观察者是否需要,推送的信息通常是目标对象的全部或部分数据,相当于在广播通信。
  • 拉模型
    目标对象在通知观察者的时候,只传递少量信息,如果观察者需要更具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据,
    一般这种模型的实现中,会把目标对象自身通过update方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。

根据上面的描述,我们上面的实现就是拉模型,下面我们实现一下推模型,基于我们自己定义的Observer接口。

/**
 * 观察者接口
 */
public interface Observer {

  void update(String content);

}

具体观察者

/**
 * 观察者实现
 */
public class ConcreteObserver implements Observer {

  @Override
  public void update(String content) {
    System.out.println("ConcreteObserver," + content);
  }
}

目标对象

public class Subject {

  private List<Observer> observers = new ArrayList<>();

  /**
   * 注册观察者
   */
  public void attach(Observer observer) {
    observers.add(observer);
  }

  /**
   * 删除观察者
   */
  public void detach(Observer observer) {
    observers.remove(observer);
  }

  /**
   * 通知所有观察者
   */
  protected void notifyObservers(String content) {
    for (Observer observer : observers) {
      observer.update(content);
    }
  }

}

具体目标对象

public class ConcreteSubject extends Subject {

  private String subjectState;

  public void setSubjectState(String subjectState) {
    this.subjectState = subjectState;
    notifyObservers(subjectState);
  }
}

主要的变化在于update方法的参数由Subject(目标对象本身)变成了content(目标对象的详细信息)。

观察者模式在Guava和Spring的实现

Guava中的EventBus

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

public class TestEventBus {

  public static void main(String[] args) {
    EventBus eventBus = new EventBus();
    eventBus.register(new SimpleEventListener());
    eventBus.post("this is a test event");
    eventBus.post(12);
  }


  static class SimpleEventListener {

    @Subscribe
    public void test(String event) {
      System.out.println("received1 the event from eventbus: " + event);
    }

    @Subscribe
    public void test2(Integer event) {
      System.out.println("received2 the event from eventbus: " + event);
    }
  }

}

输出结果为

received1 the event from eventbus: this is a test event
received2 the event from eventbus: 12

使用Subscribe注解来注册观察者,通过参数类型来区分不同的目标对象,如String类型,Integer类型,使用post方法发布一条信息,
找到这条消息的类型所对应的观察者列表,通知它们,通过反射执行方法。

Spring中的ApplicationEvent

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;

public class TestApplicationEvent {

  public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
    context.publishEvent(new OrderSuccessEvent(new Object()));
  }

  public static class OrderSuccessEvent extends ApplicationEvent {

    public OrderSuccessEvent(Object source) {
      super(source);
    }
  }

  @Configuration
  public static class BeanConfig {

    @Bean
    public SmsService smsService() {
      return new SmsService();
    }

    @Bean
    public SmsService2 smsService2() {
      return new SmsService2();
    }
  }

  public static class SmsService implements ApplicationListener<OrderSuccessEvent> {

    @Override
    public void onApplicationEvent(OrderSuccessEvent event) {
      System.out.println("SmsService " + event.getSource());
    }
  }

  public static class SmsService2 {

    @EventListener(OrderSuccessEvent.class)
    public void sendSms(OrderSuccessEvent event) {
      System.out.println("SmsService2 " + event.getSource());
    }
  }
  
}

和guava中EventBus使用方法类似,通过ApplicationEvent的具体类型来区分不同的目标对象,
可以通过注解EventListener或接口ApplicationListener两种方式来定义观察者,publishEvent方法发布一个事件,
从IOC容器中查找所有支持此event类型的观察者列表,通知它们。

总结

优点

  1. 目标对象和观察者之间抽象耦合,目标对象只知道观察者接口,不需要知道具体的观察者实现。
  2. 通过动态注册、删除观察者,实现动态联动。
  3. 支持广播通信。

缺点

  1. 如果目标对象和观察者之间存在循环依赖,可能导致系统崩溃。
  2. 如果忘记删除不必要的观察者,就会误发送消息,容易导致误操作。

本质

观察者模式的本质是触发联动。在程序运行期间动态的注册和删除观察者,可以变相的实现添加和删除某些功能处理。

使用场景

  1. 一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化,这时就可以使用观察者模式,将这两者封装成观察者和目标对象。
  2. 在更改一个对象的时候,需要同时连带改变其他的对象,但不知道具体有哪些对象需要被连带改变。
  3. 当一个对象必须通知其他对象,但希望这个对象和被通知对象之间松散耦合。

参考

大战设计模式【2】—— 观察者模式
设计模式的征途—15.观察者(Observer)模式
设计模式(十八)——观察者模式(JDK Observable源码分析)
观察者模式(Observer模式)详解
Spring事件监听机制
研磨设计模式-书籍

posted @ 2021-08-18 21:54  strongmore  阅读(228)  评论(0编辑  收藏  举报