观察者模式

定义与结构

观察者模式又名发布-订阅(publish-Subscribe)模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。

结构:

  • 抽象目标角色:目标角色知道它的观察者,可以有任意多个观察者观察同一个目标。并且提供注册和删除观察者对象的接口。目标角色往往由抽象类或者接口来实现。
  • 抽象观察者角色:尾那些在目标发生改变时需要获得通知的对象定义一个更新接口。抽象观察者角色主要由抽象类或者接口来实现。
  • 具体目标角色:将有关状态存入各个具体观察者对象。当它的状态发生改变时,向它的各个观察者发出通知。
  • 具体观察者角色:存储有关状态,这些状态应与目标状态保持一致。实现Observer的更新接口以使自身状态与目标状态保持一致。

范例:

<被观察者>

// Observable相当于一个容器--提供一些基本的方法
public class People extends Observable {
    private String name;
    private String sex;
    private int age;

    public People(String name, String sex, int age){
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        // 手动设置改变
        this.setChanged();
        // 属性改变时 通知观察者
        this.notifyObservers();
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
        this.notifyObservers();
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
        // 手动设置改变
        this.setChanged();
        this.notifyObservers();
    }
}

<观察者>

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

public class MyObserver implements Observer {

    // 通知的调用方法
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("对象发生变化");
    }
}

测试

public class MainClass {
    public static void main(String[] args) {
        People people = new People("张三", "man", 21);
        // 注册观察者
        people.addObserver(new MyObserver());

        people.setAge(11);

    }
}

/* output:
 * 对象发生变化
 */

引用《重学Java设计模式》中的案例:

在实际的业务中也会常用到一些观察者的模式或者组件,例如常见的MQ服务、事件监听总线,事件监听总线让主线服务与其他辅线业务服务分离,使系统降低耦合和增强扩展性,也会使用观察者模式处理。

// 核心业务--被观察者
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 abstract class LotteryService {

    private EventManager eventManager;

    public LotteryService() {
        // 初始化事件管理器
        eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);

        // 向事件管理器注册MQ监听器
        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 MinibusTargetService minibusTargetService = new MinibusTargetService();
    
    // 实现摇号的具体实现--为以后更改算法留下了空间,同时也屏蔽了非核心的流程
    @Override
    protected LotteryResult doDraw(String uId) {
        // 摇号的具体实现
        String lottery = minibusTargetService.lottery(uId);
        // 结果
        return new LotteryResult(uId, lottery, new Date());
    }

}

@Test
public void test(){
    LotteryService lotteryService = new LotteryServiceImpl();
    LotteryResult result = lotteryService.drow("balabala");
}

 事件监听接口定义

public interface EventListener{
    // 如果参数result是变化的可以使用泛型<T>
    void doEvent(LotteryResult result);
}

// 具体两个监听器事件
public class MessageEventListener implements EventListener {
    public void doEvent(LotteryResult result) {
        System.out.println("MessageEventListener");
    }

}

public class MQEventListener implements EventListener {
    public void doEvent(LotteryResult result) {
       System.out.println("MQEventListener");
    }

}

// 观察者容器--提供基本的订阅、取消订阅、通知方法
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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, null);
        }
    }

    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);
        }
    }

}

 从上面代码可以分为三大块看:事件监听、事件处理、核心业务流程。另外在业务流程中LotteryService定义的是抽象类,因为这样可以通过抽象类将事件功能屏蔽,外部业务流程开发者不需要知道具体的通知操作。

一般在开发中会把主线流程开发完成后,再使用通知的方式处理辅助流程,可以是异步的。

Spring中用到的观察者模式 

Spring事件驱动模型是观察者模式的一个经典应用,这在很多场景可以解耦代码。Spring事件驱动模型中的三种角色:

事件角色:

ApplicationEvent(org.springframework.context)充当事件的角色,为一个抽象类,继承之java.util.EventObject并且实现了java.io.Serializable接口。

Spring中默认存在以下事件,都是对ApplicationContextEvent的实现(继承自ApplicationContextEvent):

  • ContextStartedEvent:ApplicationContext启动后触发的事件。
  • ContextStoppedEvent:ApplicationContext停止后触发的事件。
  • ContextRefreshedEvent:ApplicationContext初始化或刷新完成后触发的事件;
  • ContextClosedEvent:ApplicationContext关闭后触发的事件;

ApplicationEvent-Subclass

 事件监听者角色:

ApplicationListener继承自jdk的EventListener,充当了事件监听者角色。这个接口只有一个onApplicationEvent()方法,该方法接受一个ApplicationEvent或其子类作为参数,在方法中可以通过不同的Event类的判断进行相应处理,当事件触发时所有的相应监听器都会收到消息。

package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E var1);
}

事件源

ApplicationContext是Spring中的全局容器,翻译过来就是"应用程序上下文",实现了ApplicationEventPublish接口。主要负责读取bean之间的依赖关系,负责整个bean的生命周期,即是IOC容器。

public interface ApplicationEventPublisher{
    void publishEvent(ApplicationEvent event);
}

public void publishEvent(ApplicationEvent event){
    Assert.notNull(event, "Event must not be null");
    if (logger.isTraceEnable()){
        logger.trace("publishing event in " + getDisplaName() + ":" + event);
    }
    
    getApplicationEventMulticaster().multicastEvent(event);
    if (this.parent != null){
        this.parent.publishEvent(event);
    }
}

ApplicationEventMulticaster抽象类【事件源中publishEvent方法需要调用其方法getApplicationEventMulticaster()】属于事件广播器,它的作用是把ApplicationContext发布的Event广播给所有的监听器。

范例:

1. 定义监听事件

public class TestEvent extends AppliationEvent{
    public String msg;
    
    public TestEvent(Object source){
        suepr(source);
    }
    
    public TestEvent(Object source, String msg){
        super(source);
        this.msg = msg;
    }
    
    public void print(){ 
        System.out.println(msg);
    }
}

2.定义监听器

public class TestListener implements ApplicationListener{
    
    // 实现了ApplicationListener方法
    public void onApplicationEvent(ApplicationEvent event){
        if (event instancef TestEvent){
            TestEvent testEvent = (TestEvent) event;
            testEvent.print();
        }
    }
}

3.添加配置文件

<bean id = "testListener" class="com.test.event.TestListener" />

4.测试

public class Test{
    public static void main(String[] args){
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath: applicationContext.xml");
        TestEvent event = new TestEvent("hello", "msg");
        context.publishEvent(event);
    }
}

当程序运行时,Spring会将发出的TestEvent事件转给我们自定义的TestListener进一步处理。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值