三种方式实现观察者模式 及 Spring中的事件编程模型
观察者模式可以说是众多设计模式中,最容易理解的设计模式之一了,观察者模式在spring中也随处可见,面试的时候,面试官可能会问,嘿,你既然读过spring源码,那你说说spring中运用的设计模式吧,你可以自信的告诉他,spring中的applicationlistener就运用了观察者模式。
让我们一步一步来,首先我们要知道到底什么是观察者模式,用java是如何实现的,在这里,我将会用三种方式来实现观察者模式。
什么是观察者模式
在现实生活中,观察者模式处处可见,比如
看新闻,只要新闻开始播放了,就会把新闻推送给订阅了新闻的用户,在这里,新闻就是【被观察者】,而用户就是【观察者】。
微信公众号,如果一个用户订阅了某个公众号,那么便会收到公众号发来的消息,那么,公众号就是【被观察者】,而用户就是【观察者】。
热水器,假设热水器由三部分组成,热水器,警报器,显示器,热水器仅仅负责烧水,当水温到达设定的温度后,通知警报器,警报器发出警报,显示器也需要订阅热水器的烧水事件,从而获得水温,并显示。热水器就是【被观察者】,警报器,显示器就是【观察者】。
在这里,可以看到,【观察者】已经失去自主的权利,只能被动的接收来自【被观察者】的事件,无法主动观察。【观察者】成为了“受”,而【被观察者】成为了“攻”。【被观察者】只是通知【观察者】,不关心【观察者】收到通知后,会执行怎样的动作。
而在设计模式中,又把【被观察者】称为【主题】。
在观察者设计模式中,一般有四个角色:
- 抽象主题角色(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,用来保存【观察者】的引用,同时定义了两个接口,这是java8默认接口实现的写法。这两个接口是给客户端调用的,用来【删除观察者】【增加观察者】,还提供一个方法,此方法需要被【具体主题角色】重写,用来通知各个【观察者】。
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(); } }
运行:
我们学习设计模式,必须知道设计模式的优缺点,那么观察者设计模式的优缺点是什么呢?
优点:
【主题】和【观察者】通过抽象,建立了一个松耦合的关系,【主题】只知道当前有哪些【观察者】,并且发送通知,但是不知道【观察者】具体会执行怎样的动作。这也很好理解,比如 微信公众号推送了一个消息过来,它不知道你会采取如何的动作,是 微笑的打开,还是愤怒的打开,或者是直接把消息删了,又或者把手机扔到洗衣机洗刷刷。
符合开闭原则,如果需要新增一个【观察者】,只需要写一个类去实现【抽象观察者角色】即可,不需要改动原来的代码。
缺点:
客户端必须知道所有的【观察者】,并且进行【增加观察者】和【删除观察者】的操作。
如果有很多【观察者】,那么所有的【观察者】收到通知,可能需要花费很久时间。
当然以上优缺点,是最直观的,可以很容易理解,并且体会到的。其他优缺点,可以自行百度。
lambda
在介绍这种写法之前,有必要介绍下函数式接口,函数式接口的概念由来已久,一般来说只定义了一个虚方法的接口就叫函数式接口,在java8中,由于lambda表达式的出现,让函数式接口大放异彩。
我们仅仅需要修改客户端的代码就可以:
public static void main(string[] args) { subject subject = new newssubject(); subject.add(a -> system.out.println("已阅这新闻")); subject.add(a -> system.out.println("假的吧")); subject.add(a -> system.out.println("昨天就看过了")); subject.update(); }
运行结果:
利用lambda表达式和函数式接口,可以省去【具体观察者角色】的定义,但是个人认为,这并非属于严格意义上的观察者模式,而且弊端很明显:
- 客户端需要知道观察者的具体实现。
- 如果观察者的具体实现比较复杂,可能代码并没有那么清晰。
所以这种写法,具有一定的局限性。
借用大神的一句话
设计模式的出现,是为了弥补语言的缺陷。
正是由于语言的升级,让某些设计模式发生了一定的变化,除了观察者模式,还有模板方法模式、责任链模式等,都由于 lambda表达式的出现,而出现了一些变化。
jdk
在java中,本身就提供了一个接口:observer,一个子类:observable,其中observer表示【观察者】,observable表示【主题】,可以利用这两个子类和接口来实现观察者模式:
public class newsobservable extends observable { public void update() { setchanged(); notifyobservers(); } }
public class people1 implements observer { @override public void update(observable o, object arg) { system.out.println("小编真无聊"); } }
public class people2 implements observer { @override public void update(observable o, object arg) { system.out.println("开局一张图,内容全靠编"); } }
客户端:
public static void main(string[] args) { newsobservable newsobservable = new newsobservable(); newsobservable.addobserver(new people1()); newsobservable.addobserver(new people2()); newsobservable.update(); }
运行结果:
在这里,我不打算详细介绍这种实现方式,因为从java9开始,java已经不推荐这种写法了,而推荐用消息队列来实现。是不是很开心,找到一个借口不去研究observable,observer 这两个东西了。
spring中的事件编程模型
spring中的事件编程模型就是观察者模式的实现,springboot就利用了spring的事件编程模型来完成一些操作,这里暂时不表。
在spring中定义了一个applicationlistener接口,从名字就知道它是一个监听器,是监听application的事件的,那么application又是什么,就是applicationcontext,applicationcontext内置了几个事件,其中比较容易理解的是:
- contextrefreshedevent
- contextstartedevent
- contextstoppedevent
- contextclosedevent
从名称上来看,就知道这几个事件是什么时候被触发的了。
下面我演示下具体的用法,比如我想监听contextrefreshedevent事件,如果事件发生了,就打印一句话。
@component public class mylistener implements applicationlistener{ @override public void onapplicationevent(applicationevent applicationevent) { if(applicationevent instanceof contextrefreshedevent){ system.out.println("刷新了"); } } }
@configuration @componentscan public class appconfig { }
public static void main(string[] args) { annotationconfigapplicationcontext context=new annotationconfigapplicationcontext(appconfig.class); }
运行结果:
当时学习spring,看到spring提供了各式各样的接口来让程序员们对spring进行扩展,并且没有任何侵入性,我不得不佩服spring的开发者们。这里也是,我们可以看到在客户端找不到任何关于“订阅事件”的影子。
这种实现方式不是太好,可以看到我们在方法内部做了一个判断:接收到的事件是否为contextrefreshedevent。
伟大的spring还提供了泛型的applicationlistener,我们可以通过泛型的applicationlistener来完善上面的代码:
@component public class mylistener implements applicationlistener<contextrefreshedevent> { @override public void onapplicationevent(contextrefreshedevent event) { system.out.println("刷新了"); } }
我们还可以利用spring中的事件编程模型来自定义事件,并且发布事件:
首先,我们需要定义一个事件,来实现applicationevent接口,代表这是一个application事件,其实上面所说的四个内置的事件也实现了applicationevent接口:
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是干嘛的呢,作为发布者,应该需要知道我要发布什么事件,以及事件来源(是谁触发的)把,这个obj就是用来存放这样的数据的,当然,这个参数需要我们手动传入进去。setapplicationeventpublisher是spring内部主动调用的,可以简单的理解为初始化发布者。
现在就剩最后一个角色了,监听器有了,发布者有了,事件也有了,对,没错,还少一个触发者,毕竟要有触发者去触发事件啊:
@component public class service { @autowired private myeventpublish publish; public void publish() { publish.publish(new myevent(this)); } }
其中publish方法就是给客户端调用的,用来触发事件,可以很清楚的看到传入了new myevent(this),这样发布者就可以知道我要触发什么事件和是谁触发了事件。
当然,还需要把一切交给spring管理:
@configuration @componentscan public class appconfig { }
客户端:
public static void main(string[] args) { annotationconfigapplicationcontext context=new annotationconfigapplicationcontext(appconfig.class); context.getbean(service.class).publish();; }
运行结果:
这一篇博客比较简单,只是简单的应用,但是只有会了应用,才能谈源码。
这篇博客到这里就结束了,谢谢大家。
下一篇: 老爸