每个角色都对应这一个类,比如观察者模式,观察者对应着观察者类,被观察者对应着被观察者类。实际上,设计模式就是通过面向对象的特性,将这些角色解耦
观察者模式本质上就是一种订阅 / 发布的模型,从逻辑上来说就是一对多的依赖关系。什么意思呢?好比是一群守卫盯着一个囚犯,只要囚犯一有异动,守卫就必须马上采取行动(也有可能是更新状态,本质上也是一种行动),那么守卫就是观察者,囚犯就是被观察者
在一个系统中,实现这种一对多的而且之间有一定关联的逻辑的时候,由于需要保持他们之间的协同关系,所以最简便的方法是采用紧耦合,把这些对象绑定到一起。但是这样一来,一旦有扩展或者修改的时候,开发人员所面对的难度非常大,而且很容易造成Bug。那么观察者模式就解决了这么一个问题,在保持一系列观察者和被观察者对象协同工作的同时,把解耦了它们
Coding
抽象观察者角色类
public interface Observer
{
// 更新接口
public void update();
}
具体观察者角色类
public class ConcreteObserver implements Observer
{
// 观察者的状态
private String observerState = "Initial"; // 观察者初始状态,会随着被观察者变化而变化
private String name; // 观察者名称,用于标记不同观察者
private Subject concreteSubject;
// 构造观察者,并传入被主题对象,以及标识该观察者名称
public ConcreteObserver(Subject concreteSubject, String name)
{
this.concreteSubject = concreteSubject;
this.name = name;
System.out.println("我是观察者" + name +", 我的状态是" + observerState);
}
// 观察者状态随主题主题改变
public void update()
{
observerState = concreteSubject.SubjectState;
System.out.println("我是观察者" + name +", 我的状态是" + observerState);
}
}
抽象主题角色类
import java.util.List;
public abstract class Subject
{
// 用来保存注册的观察者对象
List<Observer> list = null;
String SubjectState;
// 注册观察者对象
public void attach(Observer observer){};
//删除观察者对象
public void detach(Observer observer){};
// 通知所有注册的观察者对象
public void nodifyObservers(String newState){};
}
具体主题角色类
import java.util.ArrayList;
import java.util.List;
public class ConcreteSubject extends Subject
{
private List<Observer> list = new ArrayList<Observer>();
public String SubjectState;
// 注册观察者对象
public void attach(Observer observer)
{
list.add(observer);
System.out.println("Attached an observer");
}
//删除观察者对象
public void detach(Observer observer){
list.remove(observer);
}
// 通知所有注册的观察者对象
public void nodifyObservers(String newState)
{
for(Observer observer : list)
{
observer.update();
}
}
}
客户端
public class Client
{
public static void main(String[] args)
{
// 创建主题对象
Subject concreteSubject = new ConcreteSubject();
concreteSubject.attach(new ConcreteObserver(concreteSubject, "安倍晴明"));
concreteSubject.attach(new ConcreteObserver(concreteSubject, "神乐"));
concreteSubject.attach(new ConcreteObserver(concreteSubject, "源博雅"));
concreteSubject.SubjectState = "结界突破!";
concreteSubject.nodifyObservers(concreteSubject.SubjectState);
}
}
运行结果
我是观察者安倍晴明, 我的状态是Initial
Attached an observer
我是观察者神乐, 我的状态是Initial
Attached an observer
我是观察者源博雅, 我的状态是Initial
Attached an observer
我是观察者安倍晴明, 我的状态是结界突破!
我是观察者神乐, 我的状态是结界突破!
我是观察者源博雅, 我的状态是结界突破!
观察者模式关键点
在主题(被观察者)中,定义了一个集合用来存放观察者,编写了注册attach()和移除detach()观察者的方法,这体现了一对多的关系,也提供了可以控制观察者的方式
关键点1:每个观察者需要被保存到主题(被观察者)的集合中,并且被观察者提供添加和删除的方式
观察者和被观察者之间的交互活动。在添加一个观察者时,把被主题(被观察者)对象以构造函数的形式给传入了观察者。最后主题(被观察者)执行nodifyObservers()方法,触发所有观察者的update()方法以更新状态
关键点2:被主题(被观察者)把自己传给观察者,当状态改变后,通过遍历或循环的方式逐个通知列表中的观察者
但这里有个问题,主题(被观察者)是通过构造函数参数的形式,传给观察者的,而观察者对象时被attach()到主题(被观察者)的list中
关键点3:虽然解耦了观察者和主题(被观察者)的依赖,让各自的变化不大影响另一方的变化,但是这种解耦并不彻底,没有完全解除两者之间的耦合
关键点4:在事件中,订阅者和发布者之间是通过把事件处理程序绑定到委托,并不是把自身传给对方。所以解决了观察者模式中不完全解耦的问题
委托,事件,和观察者模式之间的关系
观察者模式,必然涉及到2委托和事件这两种类型
委托
委托就是可把方法当做另一个方法参数来传递,需要注意方法签名。委托可以看做是方法的抽象,也就是方法的“类”,一个委托的实例可以是一个或者多个方法。我们可以通过+=或者-=把方法绑定到委托或者从委托移除
事件
事件是一种特殊的委托。首先事件也是委托,只是在声明事件的时候,需要加上event,如果你用reflector去看一个事件,你会发现里面就3样东西,一个Add_xxxx方法,一个Remove_xxx方法,一个委托。和上面所定义主题(被观察者)时的注册attach()和移除detach()有些联系
.Net事件机制
实际上.Net的事件机制就是观察者模式的一种体现,并且是利用委托来实现。本质上事件就是一种订阅-发布模型也就是观察者模式,这种机制中包含2个角色,一个是发布者,一个是订阅者。发布者类也就类似于主题(被观察者),发布者类包含事件和委托定义,以及其之间的关系,发布者类的对象调用事件通知其他订阅者。而订阅者类也就类似于观察者,观察者接受事件,并且提供处理的逻辑。也就是说,订阅者对象(观察者)中的方法会绑定到发布者(被观察者)对象的委托中,一旦发布者(被观察者)中事件被调用,发布者(被观察者)就会调用委托中绑定的订阅者(观察者)的处理逻辑或者说是处理程序,这就是通过观察者模式实现的事件
观察者模式问答
在普通的观察者模式中,解耦并不彻底,那么在事件的发布订阅模型中,解耦彻底吗?为什么?
答案是肯定的。因为在事件中,订阅者和发布者之间是通过把事件处理程序绑定到委托,并不是把自身传给对方。所以解决了观察者模式中不完全解耦的问题
通过委托绑定方法来实现观察者模式,会不会有什么隐患?
有的,通过+=去把方法绑定到委托,很容易忘记-=。如果只绑定不移除,这个方法会一直被引用。我们知道GC去回收的时候,只会处理没有被引用的对象,只要是还被引用的对象时不会被回收掉的。所以如果在长期不关闭的系统中(比如监控系统),大量的代码使用+=而不-=,运行时间长以后有可能会内存溢出
事件,委托,观察者模式之间的关系
委托是一种类型,事件是一种特殊的委托,观察者模式是一种设计模式,事件的机制是观察者模式的一种实现,其中订阅者和发布者通过委托实现协同工作