zoukankan      html  css  js  c++  java
  • 设计模式之观察者模式

    定义

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

    结构

    • 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事件监听机制
    研磨设计模式-书籍

  • 相关阅读:
    Windows下的符号链接
    简单谈谈存储器的容量缩水问题
    配置CKEditor和CKFinder
    CKFinder的水印控件的问题
    关于 Visual Studio 默认创建的不是公共类
    Symbian 60 按键以及对应的键值(图)
    PLC中存储器的数据类型与寻址方式
    S7200 寻址
    电工识图笔记
    S7200型号串口通信参数设置
  • 原文地址:https://www.cnblogs.com/strongmore/p/15159029.html
Copyright © 2011-2022 走看看