设计模式之观察者模式

观察者模式是相当常用的行为型设计模式,博主(也就是我)面试的时候也考到过手写观察者模式的题目,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自带观察者模式

用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们,你们的鲲鲲发微博啦...
人家真的不是娘炮了啦...
鲲鲲我爱你发表评论:鲲鲲鲲鲲我爱你,就像老鼠爱大米!

由于时间关系(困死我了明天还要上班,代码都是新鲜的刚敲的,例子都是现想的),这里的例子并没有写多个被观察者,粗略一想,一个观察者订阅多个被观察者的实现:或者在代码中判断哪个被观察者,或者这个观察者实现多个观察者接口(每个观察者接口对应一个被观察者)。

欢迎有大佬指错!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值