现在的位置: 首页 > 综合 > 正文

观察者模式

2014年01月02日 ⁄ 综合 ⁄ 共 5639字 ⁄ 字号 评论关闭

一、引子

  1.     小雪是一个非常漂亮的女孩,漂亮的女孩总是有很多的追求者,而且追求者的队伍在不断的变动,随时有人进入这个队伍,也有人退出。男孩们追求女孩时总是表现出120%的关心,当小雪私自游玩时总是不断收到追求者询问小雪位置变动的消息,小雪也不胜其烦,但小雪是如此的一个善良的女孩,她总是打断自己正常的生活回复男孩们的消息。而男孩们由于要不断的关心小雪的位置变化也弄的精疲力竭,而且还影响正常的工作。
    在这样一个简单的故事场景中我们发现了什么?来看看小雪和男孩们的烦恼:
    男孩们必须不断的询问小雪的位置变化,从而打断正常的工作 
     小雪也要不断的接受男孩们的询问,有的时候小雪的位置并没有发生变化,还是要不断的回复男孩们的询问,也影响正常的工作。
    如果给各个男孩们回复问题的方式都不尽相同,小雪还要知道不同的回复方式,而且不断的有新的男孩们增加进来,还不知道未来有什么新的回复方式。

    看到这么多烦恼,我们创意无限的Nokia公司给小雪和男孩们提出了解决方案:
    Nokia公司荣誉出品了一款带有GPRS功能的手机,该手机保存着一个订阅位置变化短信通知的电话列表,当该手机检测到位置发生变化就会向这个订阅列表里的所有手机发送短信。看到Nokia这个解决方案,男孩们和小雪都应该松一口气,他们各自都可以按照自己正常的生活习惯,只有状态发生变化时候各自才会进行通信。

 

二、定义与结构

观察者(Observer)模式又名发布-订阅(Publish/Subscribe)模式。GOF给观察者模式如下定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

在这里先讲一下面向对象设计的一个重要原则——单一职责原则。因此系统的每个对象应该将重点放在问题域中的离散抽象上。因此理想的情况下,一个对象只做一件事情。这样在开发中也就带来了诸多的好处:提供了重用性和维护性,也是进行重构的良好的基础。

因此几乎所有的设计模式都是基于这个基本的设计原则来的。观察者模式的起源我觉得应该是在GUI和业务数据的处理上,因为现在绝大多数讲解观察者模式的例子都是这一题材。但是观察者模式的应用决不仅限于此一方面。

下面我们就来看看观察者模式的组成部分。

1)       
抽象目标角色(Subject):目标角色知道它的观察者,可以有任意多个观察者观察同一个目标。并且提供注册和删除观察者对象的接口。目标角色往往由抽象类或者接口来实现。

2)       
抽象观察者角色(Observer):为那些在目标发生改变时需要获得通知的对象定义一个更新接口。抽象观察者角色主要由抽象类或者接口来实现。

3)       
具体目标角色(Concrete Subject):将有关状态存入各个Concrete Observer对象。当它的状态发生改变时,向它的各个观察者发出通知。

4)       
具体观察者角色(Concrete Observer):存储有关状态,这些状态应与目标的状态保持一致。实现Observer的更新接口以使自身状态与目标的状态保持一致。在本角色内也可以维护一个指向Concrete Subject对象的引用。

放上观察者模式的类图,这样能将关系清晰的表达出来。

       可以看得出来,在Subject这个抽象类中,提供了上面提到的功能,而且存在一个通知方法:notify。还可以看出来Subject和ConcreteSubject之间可以说是使用了模板模式(这个模式真是简单普遍到一不小心就用到了)。

       这样当具体目标角色的状态发生改变,按照约定则会去调用通知方法,在这个方法中则会根据目标角色中注册的观察者名单来逐个调用相应的update方法来调整观察者的状态。这样观察者模式就走完了一个流程。

       在下面的例子中会更深刻的体验到这个流程的。

 

三、举例

 

观察者模式的解决方案

    在上面Nokia的解决方案中就透露出观察者模式的思想:观察者模式定义了对象之间一对多的依赖,当这个对象的状态发生改变的时候,多个对象会接受到通知,有机会做出反馈。在运行的时刻可以动态的添加和删除观察者。

    带着这个定义我们来看看尝试实现上面的观察者模式

    首先在观察者模式中我们必须定义一个所有“观察者”都必须实现的接口,这样被观察者向观察者发送消息的时候就可以使用统一的方式,这也符合面相对象原则中的面向接口编程:

 1//所有观察者都必须实现 
 2public interface IBoy 
 3
 4//向男孩们显示小雪位置情况,也就是向观察者发送消息,观察者还可以对此做出反馈 
 5void Show(string address); }
 
 6
 7using System; 
 8//男孩A,一个观察者 
 9public class BoyA : IBoy 
10
11public void Show(string address) 
12
13//假设经过处理后为韩文的地址 
14Console.WriteLine("A:"+address); 
15}
 
16}
 
17using System; 
18//男孩B,又一个观察者 
19public class BoyB : IBoy 
20
21public void Show(string address) 
22
23//假设经过处理后为英语的地址 
24Console.WriteLine("B:"+address); 
25}
 
26}
 

下面看看小雪的实现,也就是被观察者,主要看看那个订阅的电话列表和怎样将消息通知给观察者.

 1using System; 
 2using System.Collections.Generic; 
 3public class GPRSMobile 
 4
 5//保存一个观察者列表 
 6private List<IBoy> boys = null
 7private string address = ""
 8public GPRSMobile() 
 9
10boys = new List<IBoy>(); 
11}
 
12//添加观察者 
13public void AddBoy(IBoy b) 
14
15boys.Add(b); 
16}
 
17public void RemoveBoy(IBoy b) 
18
19boys.Remove(b); 
20}
 
21//通知 
22private void Notify(string address) 
23
24for (int i = 0; i < boys.Count; i++
25
26boys[i].Show(address); 
27}
 
28}
 
29//位置发生变化 
30public void OnAddressChanaged(string newAddress) 
31
32//假设这里的地址是中文形式的 
33Notify(newAddress); 
34}
 
35}
 

看到上面的代码例子,我们可以给观察者模式的实现总结出这样几条规律:第一,被观察者必须保存着一个观察者列表。第二,所有的观察者必须实现一个统一的接口。

    那观察者模式到底有哪些好处呢?在上面的例子中我们可以看到被观察者仅仅依赖于一个实现了观察者接口的列表,我们可以随时的向这个列表添加观察者,而被观察者无须关注观察者是如何实现的。当我们向观察者族中添加一个新的观察者,被观察者无须作任何改变,新的类型只要实现了观察者接口即可。
在上面的描述中我们仿佛看到了面向接口编程无穷的力量,面向接口编程使实现依赖于接口,也就是实现依赖于抽象。这样在被依赖对象发生改变的时候,只要接口没有发生变化时,依赖对象无须作任何变动。

   在现实中存在着很多观察者模式的实例。比如在这个全民炒股的时代,每个持有股票的人总是不断的关注着自己所买的股票的走势,有人天天呆在交易大厅里看着屏幕上股票价格的走势,有人在工作时间盯着电脑里股票软件,为此很多公司采取各种各样的政策来制止这种行为,这样不仅影响正常的上班,且股票交易大厅常常人满为患。如果有这样一个服务,只要你订阅一个短信,这个服务就会在你所关注的股票价格发生变动的时候短信通知你,这样你就可以按照正常的顺序来做你的工作。

.net中的观察者模式

    在.net中,微软给我们带来一个更好的观察者模式的实现:事件-委托.

    在Gof的观察者模式中(姑且称之为经典设计模式吧),观察者必须实现一个统一的接口,在.net里这个接口由委托的签名来保证了,.net里的委托就是一个安全的函数指针(之所以说安全是与以前的C指针相比的,C的函数指针并不包括函数的签名比如参数等东西,所以可以传递一个并不是你期望的函数进去,导致运行时出错,由于这种错误在运行时发生,很难检查出来)。Ok,现在以一个.net的委托-事件的例子结束今天的观察者模式吧。

    描述:这是一个控制台程序,程序接收一个0到100之间整型的输入,程序接收到输入后开始一个从0到100的循环,当循环到你输入的数字的时候做一些处理,我们将以两种方式来描述这个实例,先用常规的方式,然后采用委托-事件的方式

 1public class Program 
 2
 3static void Main(string[] args) 
 4
 5Console.WriteLine("Please Input a 0-100 Number:"); 
 6int input = Console.Read(); 
 7if (input < 0 || input > 100
 8
 9Console.WriteLine("Error"); 
10}
 
11for (int i = 0; i <= 100; i++
12
13if (i == input) 
14
15//屏幕输出 
16Console.WriteLine(i.ToString()); 
17//弹出提示框 
18MessageBox.Show(i.ToString()); 
19//可能还有其他处理 
20}
 
21}
 
22}

23 }
 

看到这个例子有什么感觉?循环的代码和处理的代码混在一起,可能还有未知的处理方式添加进来。耦合度非常高。再看看.net的处理方式吧

namespace Observer
{
    
//定义一个委托,这里定义了观察者方法的签名,就是一个协议吧 
    public delegate void NumberEventHandler(object sender, NumberEventArgs e);
    
//要传递哪些参数到观察者?在这里定义,注意,要继承自EventArgs 
    public class NumberEventArgs : EventArgs
    
{
        
public NumberEventArgs(int number)
        
{
            _number 
= number;
        }

        
private int _number;
        
public int Number
        
{
            
get return _number; }
            
set { _number = value; }
        }

    }

    
//观察者模式中的主题 
    public class Subject
    
{
        
//定义一个事件,就是委托的实例了 
        public event NumberEventHandler NumberReached;
        
public void DoWithLoop(int number)
        
{
            
for (int i = 0; i <= 100; i++)
            
{
                
//触发事件的条件到了 
                if (i == number)
                
{
                    NumberEventArgs e 
= new NumberEventArgs(i);
                    OnNumberReached(e);
                }

            }

        }

        
//注意,这个方法定义为保护的,虚拟的,代表子类还可以进行覆盖,改变触发事件的行为 
        
//甚至可以不触发事件 
        protected virtual void OnNumberReached(NumberEventArgs e)
        
{
            
//判断事件是否为null,也就是是否绑定了方法 
            if (NumberReached != null)
                NumberReached(
this, e);
        }

    }

    
public class MainProgram 
    

        
public static void Main() 
        

            Console.WriteLine(
"Please Input a 0-100 Number:");
            
int input = Console.Read(); 
            
if (input < 0 || input > 100
            

抱歉!评论已关闭.