观察者模式

模式定义

我们知道面向对象的一个主题就是类实例的实体化,即每个实体都有自己的状态和行为,而仿照真实世界的不同客观实体之间是存在诸多联系的,则一个很常见的对象联动方式就是反馈行为的发生。最常见的场景如:班车进站,众旅客上车,这是一个典型的对象反馈行为。由此产生的一种描述该现象的设计模式,称之为观察者模式,也有其他的名字如发布-订阅模式、模型-视图模式等。

观察者模式作为一种对象行为型模式,用于监控对象状态,并根据对象状态的改变而通知与其存在依赖关系的其他对象,所谓的“通知”其实就是产生反馈效果,所以该模式通常应用于一对一和一对多的对象关系中。

模式示例

以上面提到的班车进站和乘客上车为例,结构如下:


1、Observer作为观察者接口,只有一个update方法,作为抽象的响应函数,重点是其参数

2、passenger作为具体观察者,即等待车辆的乘客,实现update函数,即上车动作,这里的参数是观察目标,即车辆的编号(标志车辆)

3、Observable作为观察目标类,包含一个对观察者的集合,以实现在自身状态发生改变时,完成“通知”功能

4、Bus继承Observable类,也就是具体的目标类,这里模仿的是汽车进站场景,所以自身提供了一个进站函数(覆盖空的父类change函数),如果是汽车的其他场景,则需要提供其他类型的函数,作为改变状态,产生反馈源信息的条件。

示例代码

public class t{
	public static void main(String[] args){
		Observer pass1=new passenger("张三");//构造三个观察者
		Observer pass2=new passenger("李四");
		Observer pass3=new passenger("王五");

		Observable car=new Bus("916-1号线");//一个观察目标
		car.add(pass1);//添加观察关系
		car.add(pass2);
		car.add(pass3);
		car.change();//观察目标更新状态
	}
}
interface Observer{//抽象观察者类
	void update(String id);
}
class passenger implements Observer{//具体观察者类
	private String name=null;
	public passenger(String name){
		this.name=name;
	}
	public void update(String id){
		System.out.println(name+" get in the car NO:"+id);
	}
}
class Observable{//观察目标类
	private List<Observer> observers=null;//观察者的属性集合
	protected String id=null;
	public Observable(String id){
		this.id=id;
		this.observers=new ArrayList<Observer>();
	}
	public void add(Observer o){
		this.observers.add(o);
	}
	public void delete(Observer o){
		this.observers.remove(o);
	}
	public void Notify(){//发出更新通知
		for(int i=0;i<observers.size();i++){
			observers.get(i).update(this.id);
		}
	}
	public void change(){}//待覆盖的更新状态事件
}
class Bus extends Observable{//具体目标类
	public Bus(String id){
		super(id);
	}
	public void change(){//更新事件
		System.out.println("列车进站。。。");
		Notify();
	}
}
此处只是演示了目标对象的更新事件,对观察者的通知机制,并没有在目标对象中包含具体的状态,也没有进行对并发的处理。

更新函数参数

1、关于示例中的update函数的参数,在此例中观察目标并没有包含具体的状态,所以在通知观察者的函数中并没有提供自身引用作为参数,简单说就是,观察者对于观察目标的更新,只需要根据自身做出更新即可,不需要借助观察目标的状态。

2、在一些特殊的或者极为简单的场景中这是可以的,但是在简单如上例中所说,乘客等车这样单调的情形中,仍然需要传递一个车辆id作为参数(多个车辆中标志乘客等待的是某一个),所以通常的update函数是需要提供参数的(下面有Java API参考)。例如在Java的事件处理中,需要有事件源(观察目标)、事件处理程序(观察者),还要有事件对象本身(需要传递的对象),这里的事件处理程序需要针对事件对象做出操作,即update中需要提供事件对象作为参数。

3、更为一般的情况则是,事件处理程序既需要事件对象,也需要事件源的状态,然后才能做出处理,即需要同时提供观察目标的状态和发生的事件对象,即update中需要包含两个参数。

参考源代码

JDK中提供有对观察者模式的支持,在java.util包中提供有Observer接口和Observable类

观察者接口

public interface Observer {
/*
		当观察目标状态更新时,观察者的更新函数
		包括两个参数,一个是观察目标,用以提供观察目标的状态
		另一个则是事件对象,如果只需要根据目标做出更新,则arg为null
	 */
    void update(Observable o, Object arg);
}
观察目标类

public class Observable {
    private boolean changed = false;//状态更新的标志
    private Vector obs;//用以保存观察者的集合

    /** Construct an Observable with zero Observers. */

    public Observable() {
        obs = new Vector();
    }

	/*
		在添加观察者的函数中可以看出,即便vector作为所谓的线程安全集合
		自带的同步机制也只是实现addElement方法是同步方法,并不能保证addObserver的同步
		所以addObserver操作仍然需要synchronized修饰
		在去除重复观察者方面,利用vector的contains方法,而不是set集合,原因猜测
		1.对观察者模式的支持提出的时间比较早,跟vector一样古老,所以用的是vector,
		至于没有更改估计跟vector一直用Enumeration至死都没有改用Iterator一样,
		太古老,所以直接放弃不用了。没有考证,只是猜测
		2.线程安全集合,虽然此处没看出在同步方法之上再覆盖一层同步方法有何益处(
		除otifyObservers之外,所有方法都是synchronized修饰的同步方法,且notifyObservers
		函数中是复制出新集合,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);
    }
/*
		如果只需要传递观察目标接口,则第二个参数为null
		甚至专门提供了一个无参的notifyObservers方法来实现
	 */
    public void notifyObservers() {
        notifyObservers(null);
    }
public void notifyObservers(Object arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Object[] arrLocal;
		/*
			这段代码的作用是在观察目标状态更新后,将原来的观察者集合复制出来为allLocal
			这样可以保证在通知所有观察者的过程中,原来的集合仍然可以同时添加观察者或者
			删除观察者,类似于一种读写分离或者写时复制的思想(此时是在通知观察者过程中,
			原有vector可以存在其他行为,即复制完成后。复制过程是全程加锁的,vector的添加、
			删除观察者操作也是加锁的,所以复制过程是同步的)
			并发中存在情况有
			1.在复制过程之后,通知操作完成之前,此时新添加的观察者不会收到更新通知
			2.在复制过程之后,通知操作完成之前,此时新删除的观察者也会收到更新通知
			源码说明如下:
		*/
        synchronized (this) {
            /* We don't want the Observer doing callbacks into
             * arbitrary code while holding its own Monitor.
             * The code where we extract each Observable from
             * the Vector and store the state of the Observer
             * needs synchronization, but notifying observers
             * does not (should not).  The worst result of any
             * potential race-condition here is that:
             * 1) a newly-added Observer will miss a
             *   notification in progress
             * 2) a recently unregistered Observer will be
             *   wrongly notified when it doesn't care
             */
            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();
    }
protected synchronized void setChanged() {
        changed = true;
    }
protected synchronized void clearChanged() {//恢复更新状态
        changed = false;
    }
public synchronized boolean hasChanged() {
        return changed;
    }
public synchronized int countObservers() {
        return obs.size();
    }
}

总结

观察者模式适用于对象的状态更新会产生对其他对象的影响,即需要其他对象针对该对象的更新操作作出自己的更新/调整操作。较好的类之间的耦合情况应该是,一个类实例的更新只对一个或很少的其他实例产生影响。

参考设计模式开闭原则,关于update函数的参数问题,如果无参数则具体目标类与具体观察者类无依赖关系,只是针对抽象类编程,满足开闭原则;如果需要以具体目标类作为参数,则如果需要的状态为抽象目标类的共有状态,则仍可以满足开闭原则,面向抽象编程,如果需要提供的目标类状态信息为具体目标类私有,则此时观察者类面向具体目标类编程,违反开闭原则,此种情形下抽象目标类的作用仅限于复用,不提供抽象编程的帮助。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值