观察者模式(Observer Pattern)(一):观察者模式介绍

一、意图


定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。



二、适用性


《设计模式》中提到在以下任一情况下可以使用观察者模式:


1.当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。


2.当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。


3.当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。



三、组成


——抽象主题(Subject)角色

        把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者。一般用接口或抽象类来实现抽象主题角色。


——抽象观察者(Observer)角色

       为具体的观察者定义一个更新接口,在得到主题的通知时更新自己。


——具体主题(Concrete Subject)角色

        在具体主题内部状态改变时,给所有登记过的观察者发出通知。是抽象主题的子类(或实现)。


——具体观察者(Concrete Observer)角色

        该角色实现抽象观察者角色所要求的更新接口,以便本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。



四、结构


上述的Subject和Observer可以用java的interface或abstract class实现,在设计模式中“接口”并不一定指的就是java(或其他语言)中的interface。

有时我们或许并不需要在主题或观察者中加入记录本身状态的State属性,在观察者中或许也不需要保存一个Subject类型的指向主题的引用。观察者模式的重点在于:使用接口或抽象类实现具体主题与具体观察者的松耦合、主题通过维持Observer类型的集合与观察者实现一对多依赖、主题增加删除某观察者、主题改变时通知更新其所有的观察者。



五、实现


1.最简单的实现


本简单实现没有在主题和观察者中加入记录本身状态的属性,并且在观察者中也没有保存一个指向主题的引用,只是实现了观察者模式的基本特点,可以根据自身的需求对其扩展(比如:加入状态或指向主题的引用等)。


(1)抽象主题角色

public interface Subject
{
	public void registerObserver(Observer o);

	public void removeObserver(Observer o);

	public void notifyObservers();
}

此处使用接口而不是用抽象类实现抽象主题,是因为java(或C#等)是单继承的,当我们使用抽象类实现抽象主题时,那么某类想具有Subject的行为的同时又想具有另一超类的行为时,就会陷入两难的境地。


(2)具体主题角色

public class ConcreteSubject implements Subject
{
	List<Observer> observers = new ArrayList<Observer>();
	
	@Override
	public void registerObserver(Observer o)
	{
		if (o == null)
			throw new NullPointerException();
		if (!observers.contains(o))
		{
			observers.add(o);
		}
	}

	@Override
	public void removeObserver(Observer o)
	{
		observers.remove(o);

	}

	@Override
	public void notifyObservers()
	{
		for (Observer o : observers)
			o.update();
	}
}

(3)抽象观察者角色

public interface Observer
{
	public void update();
}


(4)具体观察者角色

public class ConcreteObserver implements Observer
{
	@Override
	public void update()
	{
	  //加入hashCode以区别不同的对象
      System.out.println(this.hashCode()+"  says: I'm notified !");
	}

}

(5)客户端测试:

public class Client
{
	public static void main(String[] args)
	{
		Subject subject = new ConcreteSubject();

		Observer o1 = new ConcreteObserver();
		Observer o2 = new ConcreteObserver();
		Observer o3 = new ConcreteObserver();

		subject.registerObserver(o1);
		subject.registerObserver(o2);
		subject.registerObserver(o3);
		subject.registerObserver(o1);//测试重复注册

		subject.notifyObservers();
	}
}


2.增强:加入主题状态,并在观察者中保存对主题的引用等


(1).抽象主题角色

public abstract class Subject
{
	private List<Observer> observers = new ArrayList<Observer>();

	public void registerObserver(Observer o)
	{
		if (o == null)
			throw new NullPointerException();
		// 避免同一个观察者注册多次
		if (!observers.contains(o))
		{
			observers.add(o);
		}
	}

	public void removeObserver(Observer o)
	{
		observers.remove(o);
	}

	public void notifyObservers()
	{
		for (Observer o : observers)
		{
			o.update();
		}
	}
}

这里使用的是抽象类实现的抽象主题角色,因为registerObserver、removeObserver、notifyObservers是所有子类公共的部分,将它们实现在超类中是理所当然的,可以复用父类的代码,子类可以更简洁地实现具体主题。但对于java这种单继承的语言,会出现我们前面所说的两难境地。


(2).抽象观察者角色

public interface Observer
{
	public void update();
}

依然使用接口实现抽象观察者角色,主题与观察者的松耦合的就是体现在这里,对于java等单继承的语言,实现抽象观察者时,使用接口优于使用抽象类。


(3).具体主题角色

public class ConcreteSubject extends Subject
{
	Object state;//具体主题本身的状态
	
	public Object getState()
	{
		return state;
	}

	public void setState(Object state)
	{
		this.state = state;
	}
}

(4)具体观察者角色

public class ConcreteObserver implements Observer
{
	// 具体观察者内部维持一个ConcreteSubject类型的指向具体主题的引用
	ConcreteSubject subject;
	Object state;

	public ConcreteObserver(ConcreteSubject subject)
	{
                if(subject==null)//观察者不能监听null
                    throw new NullPointerException();
                this.subject = subject;
		this.subject.registerObserver(this);
	}
        //解除对主题的依赖(注册)
	public void unRegister()
	{
		this.subject.removeObserver(this);
	}

	@Override
	public void update()
	{
		state = this.subject.getState();
		// hashCode用于区别不同的观察者
		System.out.println(this.hashCode() + "I'm notified!!");
	}
}

此处具体观察者中包含一个主题类型的引用,注意是ConcreteSubject类型的,我们希望它是Subject类型的,因为这样我们就可以使用多态让具体观察者先解除对原主题的注册(因为它内部只维持了一个主题引用而不是多个)再注册到不同类型的具体主题。但是在这里,很难办到,因为State状态信息是具体主题所有的,不同的具体主题有不同的状态信息。这样的设计符合以上结构图中的描述,在《设计模式》这本经典书籍中的结构图也是这样描述的,我想,观察者模式的松耦合体现在:所有观察者都实现了Observer接口使主题不必知道具体的观察者类,只需调用它的update()方法就行。

这里,可以稍进一步修改(或许这种修改没能改变原来的状况):

        将具体观察者中的持有的主题引用改为Subject类型,而在使用ConcreteSubject时进行类型判断及强制类型转换(貌似要加入不少的if语句),《HeadFirst设计模式》中的天气报告板示例中是这样做的,见HeadFirst中的气象站的实现中的类图或具体观察者。

       还有一种方式,以上我们同步主题和观察者的状态时,使用的是:“state = this.subject.getState();”,这称为“拉”数据,即观察者根据自己的需要从主题中获取数据。然而,或许“推”数据更好一些,即将主题的状态作为参数通过update(State)传送给观察者(不管你用不用,把数据都给你啦),同样,《HeadFirst设计模式》中的天气报告板示例中是这样做的,见HeadFirst中的气象站的实现中的类图或具体观察者,java内置的对观察者模式的支持中也提供了两种选择:推或拉,见下面的内容:java对观察者模式的内置支持。


(5)测试

public class Client
{
	public static void main(String[] args)
	{
		ConcreteSubject concreteSubject = new ConcreteSubject();
		ConcreteObserver o1 = new ConcreteObserver(concreteSubject);
		ConcreteObserver o2 = new ConcreteObserver(concreteSubject);

		concreteSubject.notifyObservers();
		o1.unRegister();
		o2.unRegister();
		concreteSubject.notifyObservers();
	}
}




六、java对观察者模式的内置支持


java.util.Observable类充当观察者模式中的抽象主题角色(在这里可以将其称为“可观察者”)

java.util.Observer接口充当观察者模式中的抽象观察者角色(体现了“松耦合”)

java内置的对观察者模式的支持结构图为:



不足之处(摘自HeadFirst):java.util.Observable是一个抽象类,就像我们前面所提到的,因为java是单继承的,这使得某类不可能同时具有Observable和其他超类的行为,这限制了Observable的复用潜力(这也是在代码实现1中使用interface的原因)。另外,Observable将关键方法如setChanged()设置成protected,这意味着,:除非你继承Observable类,否则你无法创建Observable实例并组合到你自己的对象中来,这违反了“多用组合,少用继承”的原则。

(一个示例:《使用java内置的支持实现HeadFirst气象站



七、推、拉数据


“推(push)”数据指的是主题将状态信息(数据)作为参数通过update(State)方法(在Observer接口中定义)传给具体观察者,具体观察者再根据需要使用参数State中有用的信息进行同步更新。


“拉(pull)”数据指的是接到通知后,观察者根据需要从主题中提取自己需要的数据。


在java内置的对观察者模式的支持中,也提供了这两种状态(数据)的传递的方式:


java.util.Observer接口(即抽象观察者角色)中有且只有一个方法:void update(Observable o, Object arg),其中参数arg传递的就是主题(可观察者)的状态信息。


java.util.Observable类(可观察者,即我们的“抽象主题角色”)中方法notifyObservers()就是对“拉”数据的支持,方法notifyObservers(Object arg)就是对 “推”数据的支持。



八、设计原则


设计原则:为了交互对象之间的松耦合设计而努力


在观察者模式中,改变主题或观察者其中一方,并不会影响另一方,因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以自由地改变他们。
松耦合的设计之所以能让我们建立有弹性的OO系统,能够应付变化,是因为对象之间的互相依赖降到了最低。



九、其他


1.上述的主题和观察者中的状态(数据)State泛指主题(或观察者)中的状态信息,可以是一组数据,并不是只有一个Object类型的状态数据State。


2.根据自己的需要适当实现观察者模式,如:抽象主题的实现方式(接口或抽象类?)是否在主题或观察者中加入表示本身状态的属性、是否在观察者中加入一个主题类型的引用(这个引用的类型是抽象主题类型的还是具体主题类型的?)、选择传递数据的方式(推或拉?)以及是否采用类似Observable中setChanged()方法适当调整主题通知观察者的程度(是立即通知还是达到一定程度才通知还是..?)等等。


3.对于更加复杂的依赖关系的观察者模式,《设计模式》中进行了阐述,摘抄如下


封装复杂的更新语义:

当目标和观察者间的依赖关系特别复杂时, 可能需要一个维护这些关系的对象。我们称这样的对象为更改管理器(ChangeManager)。它的目的是尽量减少观察者反映其目标的状态变化所需的工作量。例如, 如果一个操作涉及到对几个相互依赖的目标进行改动, 就必须保证仅在所有的目标都已更改完毕后,才一次性地通知它们的观察者,而不是每个目标都通知观察者。

ChangeManager有三个责任:

a) 它将一个目标映射到它的观察者并提供一个接口来维护这个映射。这就不需要由目标来维护对其观察者的引用, 反之亦然。
b) 它定义一个特定的更新策略。
c) 根据一个目标的请求, 它更新所有依赖于这个目标的观察者。

下页的框图描述了一个简单的基于ChangeManager的Observer模式的实现。有两种特殊的ChangeManager。SimpleChangeManager总是更新每一个目标的所有观察者, 比较简单。相反,DAGChangeManager处理目标及其观察者之间依赖关系构成的无环有向图。当一个观察者观察多个目标时, DAGChangeManager要比SimpleChangeManager更好一些。在这种情况下, 两个或更多个目标中产生的改变可能会产生冗余的更新。DAGChangeManager保证观察者仅接收一个更新。当然,当不存在多重更新的问题时, SimpleChangeManager更好一些。ChangeManager是一个Mediator(中介者)模式的实例。通常只有一个ChangeManager, 并且它是全局可见的。这里Singleton(单例)模式可能有用。


转载请注明出处:http://blog.csdn.net/jialinqiang/article/details/8871965

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值