关于java:观察者模式从JDK到Spring

22次阅读

共计 4996 个字符,预计需要花费 13 分钟才能阅读完成。

文章收录在 GitHub JavaKeeper,N 线互联网开发必备技能兵器谱

在软件系统中常常会有这样的需要:如果一个对象的状态产生扭转,某些与它相干的对象也要随之做出相应的变动。

  • 微信公众号,如果一个用户订阅了某个公众号,那么便会收到公众号发来的音讯,那么,公众号就是『被观察者』,而用户就是『观察者』
  • 气象站能够将每天预测到的温度、湿度、气压等以布告的模式公布给各种第三方网站,如果天气数据有更新,要可能实时的告诉给第三方,这里的气象局就是『被观察者』,第三方网站就是『观察者』
  • MVC 模式中的模型与视图的关系也属于察看与被察看

观察者模式是应用频率较高的设计模式之一。

观察者模式蕴含察看指标和观察者两类对象,一个指标能够有任意数目的与之相依赖的观察者,一旦察看指标的状态产生扭转,所有的观察者都将失去告诉。

定义

观察者模式(Observer Pattern):定义对象间一种一对多的依赖关系,使得当每一个对象扭转状态,则所有依赖于它的对象都会失去告诉并自动更新。

观察者模式是一种 对象行为型模式

观察者模式的别名包含公布 - 订阅(Publish/Subscribe)模式、模型 - 视图(Model/View)模式、源 - 监听器(Source/Listener)模式或隶属者(Dependents)模式。

细究的话,公布订阅和观察者有些不同,能够了解成公布订阅模式属于狭义上的观察者模式。

角色

  • Subject(指标):被观察者,它是指被察看的对象。从类图中能够看到,类中有一个用来寄存观察者对象的 Vector 容器(之所以应用 Vector 而不应用 List,是因为多线程操作时,Vector 在是平安的,而 List 则是不平安的),这个 Vector 容器是被观察者类的外围,另外还有三个办法:attach 办法是向这个容器中增加观察者对象;detach 办法是从容器中移除观察者对象;notify 办法是顺次调用观察者对象的对应办法。这个角色能够是接口,也能够是抽象类或者具体的类,因为很多状况下会与其余的模式混用,所以应用抽象类的状况比拟多。
  • ConcreteSubject(具体指标):具体指标是指标类的子类,通常它蕴含常常产生扭转的数据,当它的状态产生扭转时,向它的各个观察者发出通知。同时它还实现了在指标类中定义的形象业务逻辑办法(如果有的话)。如果毋庸扩大指标类,则具体指标类能够省略。
  • Observer(观察者):观察者将对察看指标的扭转做出反馈,观察者个别定义为 接口 ,该接口申明了更新数据的办法 update(),因而又称为 形象观察者
  • ConcreteObserver(具体观察者):在具体观察者中保护一个指向具体指标对象的援用,它存储具体观察者的无关状态,这些状态须要和具体指标的状态保持一致;它实现了在形象观察者 Observer 中定义的 update()办法。通常在实现时,能够调用具体指标类的 attach() 办法将本人增加到指标类的汇合中或通过 detach() 办法将本人从指标类的汇合中删除。

类图

再记录下 UML 类图的注意事项,这里我的 Subject 是 形象办法 ,所以用 斜体,形象办法也要用斜体,具体的各种箭头意义,我之前也总结过《设计模式前传——学设计模式前你要晓得这些》(被网上各种帖子毒害过的本人,认真记录~~~)。

实例

1、定义观察者接口

interface Observer {public void update();
}

2、定义被观察者

abstract class Subject {private Vector<Observer> obs = new Vector();

    public void addObserver(Observer obs){this.obs.add(obs);
    }
    public void delObserver(Observer obs){this.obs.remove(obs);
    }
    protected void notifyObserver(){for(Observer o: obs){o.update();
        }
    }
    public abstract void doSomething();}

3、具体的被观察者

class ConcreteSubject extends Subject {public void doSomething(){System.out.println("被观察者事件产生扭转");
        this.notifyObserver();}
}

4、具体的被观察者

class ConcreteObserver1 implements Observer {public void update() {System.out.println("观察者 1 收到信息,并进行解决");
    }
}
class ConcreteObserver2 implements Observer {public void update() {System.out.println("观察者 2 收到信息,并进行解决");
    }
}

5、客户端

public class Client {public static void main(String[] args){Subject sub = new ConcreteSubject();
        sub.addObserver(new ConcreteObserver1()); // 增加观察者 1
        sub.addObserver(new ConcreteObserver2()); // 增加观察者 2
        sub.doSomething();}
}

输入

被观察者事件产生扭转
观察者 1 收到信息,并进行解决
观察者 2 收到信息,并进行解决

通过运行后果能够看到,咱们只调用了 Subject 的办法,但同时两个观察者的相干办法都被调用了。认真看一下代码,其实很简略,就是在 Subject 类中关联一下 Observer 类,并且在 doSomething() 办法中遍历一下 Observerupdate() 办法就行了。

优缺点

长处

  • 升高了指标与观察者之间的耦合关系,两者之间是形象耦合关系
  • 指标与观察者之间建设了一套触发机制
  • 反对播送通信
  • 合乎“开闭准则”的要求

毛病

  • 指标与观察者之间的依赖关系并没有齐全解除,而且有可能呈现循环援用
  • 当观察者对象很多时,告诉的发布会破费很多工夫,影响程序的效率

利用

JDK 中的观察者模式

观察者模式在 Java 语言中的位置十分重要。在 JDK 的 java.util 包中,提供了 Observable 类以及 Observer 接口,它们形成了 JDK 对观察者模式的反对(能够去查看下源码,写的比拟谨严)。but,在 Java9 被弃用了。

Spring 中的观察者模式

Spring 事件驱动模型也是观察者模式很经典的利用。就是咱们常见的我的项目中最常见的事件监听器。

1. Spring 中观察者模式的四个角色

  1. 事件:ApplicationEvent 是所有事件对象的父类。ApplicationEvent 继承自 jdk 的 EventObject, 所有的事件都须要继承 ApplicationEvent, 并且通过 source 失去事件源。

    Spring 也为咱们提供了很多内置事件,ContextRefreshedEventContextStartedEventContextStoppedEventContextClosedEventRequestHandledEvent

  2. 事件监听:ApplicationListener,也就是观察者,继承自 jdk 的 EventListener,该类中只有一个办法 onApplicationEvent。当监听的事件产生后该办法会被执行。
  3. 事件源:ApplicationContextApplicationContext 是 Spring 中的外围容器,在事件监听中 ApplicationContext 能够作为事件的发布者,也就是事件源。因为 ApplicationContext 继承自 ApplicationEventPublisher。在 ApplicationEventPublisher 中定义了事件公布的办法:publishEvent(Object event)
  4. 事件治理:ApplicationEventMulticaster,用于事件监听器的注册和事件的播送。监听器的注册就是通过它来实现的,它的作用是把 Applicationcontext 公布的 Event 播送给它的监听器列表。

2. coding~~

1、定义事件

public class MyEvent extends ApplicationEvent {public MyEvent(Object source) {super(source);
        System.out.println("my Event");
    }
}

2、实现事件监听器

@Component
class MyListenerA implements ApplicationListener<MyEvent> {public void onApplicationEvent(MyEvent AyEvent) {System.out.println("ListenerA received");
    }
}

@Component
class MyListenerB implements ApplicationListener<MyEvent> {public void onApplicationEvent(MyEvent AyEvent) {System.out.println("ListenerB received");
    }
}

3、事件发布者

@Component
public class MyPublisher implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext=applicationContext;}
    
    public void publishEvent(ApplicationEvent event){System.out.println("publish event");
        applicationContext.publishEvent(event);
    }
}

4、测试,先用注解形式将 MyPublisher 注入 Spring

@Configuration
@ComponentScan
public class AppConfig {@Bean(name = "myPublisher")
    public MyPublisher myPublisher(){return new MyPublisher();
    }
}
public class Client {

    @Test
    public void main() {ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        MyPublisher myPublisher = (MyPublisher) context.getBean("myPublisher");
        myPublisher.publishEvent(new MyEvent(this));
    }
}

5、输入

my Event
publish event
ListenerA received
ListenerB received

瞎扯

设计模式真的只是一种设计思维,不须要非得有多个观察者才能够用观察者模式,只有一个观察者,我也要用。

再举个栗子,我是做广告投放的嘛(广告投放的商品文件个别为 xml),如果我的广告位有些闲暇流量,这我得利用起来呀,所以我就从淘宝客或者拼夕夕的多多客上通过凋谢的 API 获取一些,这个时候我也能够用观察者模式,每次申请 10 万条商品,我就生成一个新的商品文件,这个时候我也能够用观察者模式,获取商品的类是被观察者,写商品文件的是观察者,当商品够 10 万条了,就告诉观察者从新写到一个新的文件。

大佬可能觉这么实现有点吃力,不必设计模式也好,或者用音讯队列也好,其实都只是一种伎俩,抉择适宜本人业务的,开心就好。

参考

https://design-patterns.readt…

https://www.cnblogs.com/jmcui…

正文完
 0