观察者模式是相当常用的行为型设计模式,博主(也就是我)面试的时候也考到过手写观察者模式的题目,Spring事件中也应用到了观察者模式(其实我还没有深入学习Spring,这是我听说的),反正很重要就是了。
先说明一下业务场景,在我们的周围有很多追星的人,她们大多数都是通过微博与爱豆进行互动,在爱豆发微博后关注了爱豆的粉丝都可以收到提醒,当然粉丝也可以取关爱豆,取关后无法获得爱豆微博更新提醒。
观察者模式
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者会收到通知并自动更新。更详细的说,被观察者保存了全部订阅了它的观察者对象的引用,并且提供了观察者订阅和取消订阅的方法。在被观察者状态改变时通知全部观察者。在上述业务场景中,爱豆就是被观察者,粉丝就是被观察者。
实现思路
基本上所有的设计原则或者说所有扩展性高,耦合度低的代码都遵守针对接口编程的设计原则,观察者模式也不例外。观察者模式中有观察者和被观察者两个角色,因此也就可以抽象出来抽象观察者和抽象被观察者角色。有了抽象被观察者角色那就可以有多个具体被观察者角色,延伸出来的问题就是一个具体观察者可以订阅多个具体被观察者么?当然可以,我关注了菜虚鲲还不能再关注一下wuli凡凡了么?但是观察者定义里提到了定义了对象之间的一对多依赖,观察者订阅多个被观察者不就变成多对多了么?这里我的理解是所谓的一对多是站在被观察者的角度,即便是一个观察者订阅了多个被观察者也是实现了多个接口,并不能算是真正意义上的多对多。如果这里暂时不能理解,可以先看下面的具体实现再回过头看,如果觉得我说的不对欢迎评论指出错误。
JDK自带观察者模式
JDK提供了Observer接口和Observable抽象类作为抽象观察者和抽象被观察者,可以先看看这两个类的API。
Observer:
public interface Observer {
//被观察者更新时调用观察者的此方法
void update(Observable o, Object arg);
}
Observable:
public class Observable {
//被观察者改变状态时,修改此参数为true
private boolean changed = false;
//保存所有订阅了此被观察者的观察者对象的引用
private Vector<Observer> obs;
public Observable() {
obs = new Vector<>();
}
//注册此被观察者的观察者
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
//注销此被观察者的指定观察者
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
public void notifyObservers() {
notifyObservers(null);
}
//改变状态时通知所有观察者
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
//注销全部观察者
public synchronized void deleteObservers() {
obs.removeAllElements();
}
//修改 修改标志为true
protected synchronized void setChanged() {
changed = true;
}
//重置修改标志位为false
protected synchronized void clearChanged() {
changed = false;
}
//被观察者状态是否改变
public synchronized boolean hasChanged() {
return changed;
}
//返回订阅此被观察者的观察者数量
public synchronized int countObservers() {
return obs.size();
}
}
从这两个接口/类的API可以看出来,我们首先要有一个Observable和Observer的实现类,具体被观察者调用addObserver()方法(事实上就是向观察者集合中添加一个元素)使指定的具体观察者订阅自己;在具体被观察者状态改变(这里的状态是自定义的)时通过遍历观察者集合的方式挨个通知具体观察者(即调用具体观察者的update()方法);具体被观察者调用deleteObserver()方法(在观察者集合中删除指定元素)使指定的观察者取消订阅。
需要特别注意的是,使用JDK自带的观察者模式,在具体被观察者状态改变时需要调用setChanged()方法修改changed为true,否则在notifyObservers()方法中并不会调用观察者的update()方法。
类图
用JDK自带的观察者模式实现以上业务场景:
具体被观察者:
class Kun extends Observable {
public void updateMicroblog(String microblog){
System.out.println("ikun们,鲲鲲发微博啦...");
setChanged();
notifyObservers(microblog);
}
public void commentOn(Ikun ikun,String comment){
System.out.println((ikun.getName()+"评论了你:"+comment));
}
}
观察者抽象类:
//这个类不属于观察者模式的任何角色,纯属在此处需要
class Ikun{
private String name;
Ikun(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
具体观察者:
//观察者1
class Ikun1 extends Ikun implements Observer {
Ikun1(String name) {
super(name);
}
@Override
public void update(Observable o, Object arg) {
Kun kun = (Kun)o;
System.out.println("菜虚鲲:" +arg);
kun.commentOn(this,"保护世界上最好的鲲鲲!");
}
}
//观察者2
class Ikun2 extends Ikun implements Observer {
Ikun2(String name) {
super(name);
}
@Override
public void update(Observable o, Object arg) {
Kun kun = (Kun)o;
System.out.println("菜虚鲲:" +arg);
kun.commentOn(this,"哥哥好棒!");
}
}
测试程序及输出结果:
测试程序:
public static void main(String[] args) {
Kun kun = new Kun();
Ikun1 ikun1 = new Ikun1("鲲鲲的屎");
Ikun2 ikun2 = new Ikun2("鲲鲲我爱你");
kun.addObserver(ikun1);
kun.addObserver(ikun2);
kun.updateMicroblog("大家好,我是练习时长两年半的菜虚鲲,喜欢唱跳rap篮球...");
}
输出结果:
ikun们,鲲鲲发微博啦...
菜虚鲲:大家好,我是练习时长两年半的菜虚鲲,喜欢唱跳rap篮球...
鲲鲲我爱你评论了你:哥哥好棒!
菜虚鲲:大家好,我是练习时长两年半的菜虚鲲,喜欢唱跳rap篮球...
鲲鲲的屎评论了你:保护世界上最好的鲲鲲!
这样就实现了以上业务场景,但这里需要注意的是Observable是抽象类而不是接口。众所周知,Java是单继承,在实际的业务场景中可能具体被观察者已经继承了其他的类,便无法使用JDK自带的观察者模式。还有就是Observer接口只有一个update()方法,这表示观察者只能被动接收,想要主动索取就需要我在上面的例子中实现的“评论”功能一样创建新的抽象类,这个抽象类与Observer有些重复。因此,更多还是需要我们自己去实现观察者模式,与JDK自带观察者模式的区别在于抽象被观察者是一个接口而不是类,并且抽象观察者也不仅限于被动接收被观察者的消息。
观察者模式
用观察者模式实现以上的业务场景:
抽象被观察者:
interface Idol {
//关注
void follow(Fans fans);
//更新微博
void updateMicroblog(String microblog);
//取消关注
void unfollow(Fans fans);
//获取微博个人信息
String getPersonalInformation();
}
抽象观察者:
interface Fans {
//爱豆发微博粉丝肯定秒评论
void commentOn(String microblog);
//获取爱豆的个人信息,主动拉取被观察者信息
void getIdolPersonalInformation(Idol idol);
}
具体被观察者:
class Kun implements Idol {
private String personalInformation;
private List<Fans> fansList;
Kun(){
fansList = new ArrayList<>();
}
@Override
public void follow(Fans fans) {
if (!fansList.contains(fans)){
fansList.add(fans);
}
}
@Override
public void updateMicroblog(String microblog) {
System.out.println("ikun们,你们的鲲鲲发微博啦...");
for (Fans fans:fansList){
fans.commentOn(microblog);
}
}
@Override
public void unfollow(Fans fans) {
fansList.remove(fans);
}
public void setPersonalInformation(String personalInformation) {
this.personalInformation = personalInformation;
}
@Override
public String getPersonalInformation() {
return personalInformation;
}
}
具体观察者:
//观察者1
class Ikun1 implements Fans {
private String name;
public Ikun1(String name) {
this.name = name;
}
@Override
public void commentOn(String microblog) {
System.out.println(microblog);
System.out.println(getName()+"发表评论:保护世界上最帅的鲲鲲!");
}
@Override
public void getIdolPersonalInformation(Idol idol) {
System.out.println(getName()+"获取到了哥哥的信息:"+idol.getPersonalInformation());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//观察者2
class Ikun2 implements Fans {
private String name;
public Ikun2(String name) {
this.name = name;
}
@Override
public void commentOn(String microblog) {
System.out.println(microblog);
System.out.println(getName() + "发表评论:鲲鲲鲲鲲我爱你,就像老鼠爱大米!");
}
@Override
public void getIdolPersonalInformation(Idol idol) {
System.out.println(getName() + "获取到了哥哥的信息:" + idol.getPersonalInformation());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试程序及输出结果:
测试程序:
public static void main(String[] args) {
Kun kun = new Kun();
Ikun1 ikun1 = new Ikun1("鲲鲲的屎");
Ikun2 ikun2 = new Ikun2("鲲鲲我爱你");
kun.follow(ikun1);
kun.follow(ikun2);
kun.updateMicroblog("大家好,我是练习时长两年半的菜虚鲲,喜欢唱跳rap篮球...");
kun.setPersonalInformation("姓名:菜虚鲲,性别:女,年龄:32");
ikun1.getIdolPersonalInformation(kun);
ikun2.getIdolPersonalInformation(kun);
System.out.println("ikun1上了六年级以后取关了鲲鲲就收不到鲲鲲的微博更新通知了...");
kun.unfollow(ikun1);
System.out.println();
kun.updateMicroblog("人家真的不是娘炮了啦...");
}
输出结果:
ikun们,你们的鲲鲲发微博啦...
大家好,我是练习时长两年半的菜虚鲲,喜欢唱跳rap篮球...
鲲鲲的屎发表评论:保护世界上最帅的鲲鲲!
大家好,我是练习时长两年半的菜虚鲲,喜欢唱跳rap篮球...
鲲鲲我爱你发表评论:鲲鲲鲲鲲我爱你,就像老鼠爱大米!
鲲鲲的屎获取到了哥哥的信息:姓名:菜虚鲲,性别:女,年龄:32
鲲鲲我爱你获取到了哥哥的信息:姓名:菜虚鲲,性别:女,年龄:32
ikun1上了六年级以后取关了鲲鲲就收不到鲲鲲的微博更新通知了...
ikun们,你们的鲲鲲发微博啦...
人家真的不是娘炮了啦...
鲲鲲我爱你发表评论:鲲鲲鲲鲲我爱你,就像老鼠爱大米!
由于时间关系(困死我了明天还要上班,代码都是新鲜的刚敲的,例子都是现想的),这里的例子并没有写多个被观察者,粗略一想,一个观察者订阅多个被观察者的实现:或者在代码中判断哪个被观察者,或者这个观察者实现多个观察者接口(每个观察者接口对应一个被观察者)。
欢迎有大佬指错!