在大多数情况下,业务逻辑层执行完表示层的命令后需要向表示层反馈信息。在我之前的《使用接口封装变化》提到的网络用户管理模块中便是使用观察者模式的一个恰当之处,类功能如下图所示。http://blog.csdn.net/kecp/archive/2009/09/29/4613618.aspx
通常软件的多个界面可能对当前登录用户的信息感兴趣,比如说当前登录状态、用户名和经验值等基本用户信息。甚至在业务逻辑层的其它部分也可能对它感兴趣,比如说聊天模块或写日记模块需要知道当前登录用状态及用户信息。对于这问题我们应该怎么办呢,让每个其它模块都拥有用户管理模块接口的一个实例?你很快就会发现变得头烦。而且因为对这个接口的调用结果可能不是马上返回的,比如网络登录连接需要一定时间并可能超时。调用者要不就阻塞要不就不停地查询,效率非常低下。那么登录成功后该由谁来通知其它对这一事件感兴趣的模块呢?如果由调用者的话,那么整个程序实现后就很杂乱而且造成不必要的强耦合。嗯,那就只有使用被调用者来进行通知了。
被调用者相当于一个被观察者,而对它感兴趣的其它模块相当于观察者。只有被观察者自己清楚自身发生了什么改变,只有它才能通知其它人关于自身的一个事件。而观察者通常对它的某一个事件感兴趣,它们希望在产生这个事件的时候能接收到通知,比如主界面、聊天模块和日记模块希望收到登录和注销事件。它们之间的接口是事件,被观察者不需要知道对它感兴趣的具体对象是什么,即登录模块类不需要知道聊天类和日记类是什么,聊天类和日记类只需向登录类注册监听自己感兴趣的事件便可。这便是观察者模式。下面我们看观察者模式怎样帮助我们来解决这一麻烦的问题。
首先我们将NetUserOP设置为被观察者,而主界面MainUI注册为它的观察者,我们只描述它的登录事件,代码如下:
//业务数据(被观察对象)
public class NetUserOP: ObservableImpl {
//被观察者中的数据
bool hasLogin;
User _user;
public void login(user) {
if( netLogin(user) == true ) //这里可能会发生阻塞
{ hasLogin =true;
_user=user;
base.NotifyObservers(_user);//将登录成功的消息通知观察者
}
}
}
//用户界面(观察者)
public class MainUI : IObserver {
public void Notify(object anObject){
Console.WriteLine("The user is:" + ((User) anObject). user.name );
}
}
//实际调用的过程
public class MainClass{
public static void Main() {
//创建观察者和被观察者
MainUI ui = new MainUI ();
NetUserOP op = new NetUserOP ();
//在被观察对象中注册观察者
op.Register(ui);
//改变被观察对象中的数据,这时被观察者会通知观察者
op.login( new User( account,password) );
//注销观察者,停止观察
stock.UnRegister(stockDisplay);
}
}
上面我们把网络登录类设置为了被观察者,当登录成功的时候,它把登录用户类User对象通知给对它感兴趣的观察者,这时观察者用户界面类将会输出该登录用户的名字。而相应的如果有实现了IObserver接口的子界面对象childUI以及聊天类对象talk,它们仅需要添加以下代码并可获得用户的登录事件通知:
op.Register( childUI ); op.Register( talk );
至此,我们解决了业务逻辑层向表示层进行反馈的问题。观察者模式频繁用于GUI应用程序中,使得层间接口清晰,上下层相互独立。代码分层本质上就是对责任分层,观察者模式将责任正确的分配到了对应的对象中,这样便于代码的维护以及扩展。