今天来聊聊Spring中的观察者模式

  • 前言
  • 一、观察者设计模式中涉及到的角色
  • 二、使用设计模式的优缺点
  • 优点
  • 缺点
  • 三、示例代码
  • 四、Spring中的观察者设计模式



前言

今天来看一看什么是观察者模式,在现实生活中,观察者模式处处可见,就以微信公众号来说吧,每当一个用户订阅了一个公众号,那么就会收到公众号发来的消息,这里公众号就是被观察的对象,用户就是观察者。而在设计模式中,被观察者被称之为主题。

一、观察者设计模式中涉及到的角色

在观察者设计模式中,一般有四个角色:

  • 抽象主题角色(Subject)
  • 具体主题角色(ConcreteSubject)
  • 抽象观察者角色(Observer)
  • 具体观察者角色(ConcreteObserver)

其中,主题需要有一个列表字段,用来保存观察者的引用,提供两个方法(虚方法),即删除观察者以及增加观察者,还需要提供一个给客户端调用的方法,通知各个观察者。

二、使用设计模式的优缺点

优点

  • 主题和观察者通过抽象,建立了一个松耦合的关系,主题只知道当前有哪些观察者,并且发送通知,但是不知道观察者具体会执行怎样的动作。
  • 符合开闭原则,如果需要新增一个观察者,只需要写一个类去实现抽象观察者角色即可,不需要改动原来的代码。

缺点

  • 客户端必须知道所有的观察者,并且进行增加观察者和删除观察者的操作。
  • 如果有很多观察者,那么所有的观察者收到通知,可能需要花费很久时间。

三、示例代码

我们可以用代码举一个简单例子,就以发布新闻为例。

// 此类不属于观察者模式必须的类,用来存放事件的信息。
public class News {
    private String title;
    private String content;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}
public interface Subject {
    List<People> peopleList = new ArrayList<>();

    default void add(People people) {
        peopleList.add(people);
    }

    default void remove(People people) {
        peopleList.remove(people);
    }

    void update();
}

抽象主题角色,在这个角色中,有一个字段peopleList,用来保存观察者的引用,同时定义了两个接口,这两个接口是给客户端调用的,用来删除观察者以及增加观察者,还提供一个方法,此方法需要被具体主题角色重写,用来通知各个观察者。

// 具体主题角色,重写了抽象主题角色的方法,
// 循环列表,通知各个观察者。

public class NewsSubject implements Subject{
    public void update() {
        for (People people : peopleList) {
            News news = new News();
            news.setContent("太阳获得了总冠军");
            news.setTitle("保罗即将获得首冠");
            people.update(news);
        }
    }
}
// 抽象观察者角色,定义了一个接口,具体观察者角色需要重写这个方法。

public interface People {
    void update(News news);
}

下面就可以定义具体的观察者了。

public class PeopleA implements People {
    @Override
    public void update(News news) {
        System.out.println("这个新闻真好");
    }
}
public class PeopleB implements People {
    @Override
    public void update(News news) {
        System.out.println("这个新闻一般般");
    }
}
public class PeopleC implements People {
    @Override
    public void update(News news) {
        System.out.println("这个新闻真无聊");
    }
}
public class Main {
    public static void main(String[] args) {
        Subject subject = new NewsSubject();
        subject.add(new PeopleA());
        subject.add(new PeopleB());
        subject.add(new PeopleC());
        subject.update();
    }
}

四、Spring中的观察者设计模式

那么,在Spring中观察者是如何体现的呢?Spring中的事件编程模型就是观察者模式的实现。在Spring中定义了一个ApplicationListener接口,用来监听Application的事件,Application其实就是ApplicationContext,ApplicationContext内置了几个事件,其中比较容易理解的是:ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent,从名称上来看,就知道这几个事件是什么时候被触发的了。接下来我们说一下如何利用Spring中的事件编程模型来定义自定义事件,并且发布事件。
首先,我们需要定义一个事件,来实现ApplicationEvent接口,代表这是一个Application事件。

public class MyEvent extends ApplicationEvent {
    public MyEvent(Object source) {
        super(source);
    }
}

还需要定义一个监听器,当然,在这里需要监听MyEvent事件。

@Component
public class MyListener implements ApplicationListener<MyEvent> {
    @Override
    public void onApplicationEvent(MyEvent event) {
        System.out.println("我订阅的事件已经到达");
    }
}

现在有了事件,也有了监听器,是不是还少了发布者,不然谁去发布事件呢?

@Component
public class MyEventPublish implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    public void publish(Object obj) {
        this.publisher.publishEvent(obj);
    }
}

发布者,需要实现ApplicationEventPublisherAware 接口,重写publish方法,方法的参数obj就是用来存放发布事件数据。setApplicationEventPublisher是Spring内部主动调用的,可以简单的理解为初始化发布者。现在就剩最后一个角色了,监听器有了,发布者有了,事件也有了,还少一个触发者。

@Component
public class Service {

    @Autowired
    private  MyEventPublish publish;

    public void publish() {
        publish.publish(new MyEvent(this));
    }
}

其中publish方法就是给客户端调用的,用来触发事件,可以很清楚的看到传入了new MyEvent(this),这样发布者就可以知道我要触发什么事件和是谁触发了事件。