观察者模式是这样的场景里来的:
在一个公司里面的员工都很喜欢炒股票,甚至在上班的时候都会这么做。但是上班炒股票被老板发现是很严重的事情,所以他们就请前台秘书放哨,每当老板进来公司的时候,前台先打个电话通知他们,然后他们立刻关掉炒股票软件,认真工作。但是有一次,老板来的时候,前台秘书正好有事没在,所以老板直接进来了,把员工A抓了个现行。当然,这件事是可以用程序实现的。
双向耦合的代码
前台秘书类:
class Secretary
{
private IList<StockObserver> observers = new List<StockObserver>();
private string action;
public string SecretaryAction
{
get { return action; }
set { action = value; }
}
//增加要通知的同事
public void Attach(StockObserver observer)
{
observers.Add(observer);
}
//通知同事
public void Notify()
{
foreach (StockObserver o in observers)
{
o.Update();
}
}
}
看股票同事类:
class StockObserver
{
private string name;
private Secretary sub;
public StockObserver(string name, Secretary sub)
{
this.name = name;
this.sub = sub;
}
public void Update()
{
Console.WriteLine("{0},{1}关闭股票行情,继续工作!", sub.SecretaryAction, name);
}
}
客户端程序:
{
//前台小姐
Secretary secretary = new Secretary();
//看股票的同事
StockObserver observer1 = new StockObserver("t1", secretary);
StockObserver observer2 = new StockObserver("t2", secretary);
//前台登记了两位同事
secretary.Attach(observer1);
secretary.Attach(observer2);
//发现老板回来
secretary.SecretaryAction = "老板回来了!";
//通知同事
secretary.Notify();
Console.ReadKey();
}
这种设计很好实现,但是他们彼此都持有对方的对象的引用,这样使得两者的耦合度很高。试想这样耦合度很高的两个类还能实现代码的复用吗?如果我只想用其中的一个类,那么还必须带着另外的一个类。
一个极端的情景就是,我有一个UI类和一个功能类,现在我同学想用一下我的功能类,把我的功能类弄到他的UI类中。那么对于耦合度很好的程序,对不起,不能用。因为我的功能类中有我的UI类的引用。所以这时,我同学要在他的UI类中做一套跟我的UI类一模一样的东西,或者去修改我的功能类来配合他自己的UI类。这样的东西很不好复用,把功能和UI类耦合在一起。
所以在设计类的时候,首先开放-封闭原则,在复用的时候,修改原有代码就说明设计不够好。其次是依赖倒转原则,我们应该让程序都依赖抽象,而不是相互依赖。
这种依赖具体的设计模式很不利于程序的扩展,比如现在同事有看NBA的,这时的前台类怎么办,是不是要在弄一个看NBA同事类的List,然后再写一个遍历,逐个通知。
解耦实践一:
//抽象观察者
abstract class Observer
{
protected string name;
protected Secretary sub;
public Observer(string name, Secretary sub)
{
this.name = name;
this.sub = sub;
}
public abstract void Update();
}
增加两个具体观察者
class StockObserver : Observer
{
public StockObserver(string name, Secretary sub)
: base(name, sub)
{ }
public override void Update()
{
Console.WriteLine("{0},{1}关闭股票行情,继续工作", sub.SecretaryAction, name);
}
}
class NBAObserver : Observer
{
public NBAObserver(string name, Secretary sub)
: base(name, sub)
{
this.name = name;
this.sub = sub;
}
public override void Update()
{
Console.WriteLine("{0},{1}关闭NBA直播,继续工作", sub.SecretaryAction, name);
}
}
前台秘书类:
class Secretary
{
private string action;
private IList<Observer> observers = new List<Observer>();
//前台状态
public string SecretaryAction
{
get { return action; }
set { action = value; }
}//SecretaryAction()
//增加
public void Attach(Observer observer)
{
observers.Add(observer);
}//Attach()
//减少
public void Detach(Observer observer)
{
observers.Remove(observer);
}//Detach()
//通知
public void Notify()
{
foreach(Observer o in observers)
{
o.Update();
}
}//Notify()
}
改到现在的程度,就是发现,前台秘书类还是一个具体的类,还不能做到面向抽象的编程,抽象出来的观察者中仍然有具体类Secrectary的引用对象。如果有一天同时们不用前台放哨了,换成让门卫放哨了,是不是观察者类中依然要改动。所以通知者也要抽象,就像前面场景中的,最后一次通知同事们的不是前台秘书,而是老板,不管怎样,前台秘书和老板都是通知者。
解耦实践二
增加抽象通知者接口:
interface Subject
{
void Attach(Observer observer);
void Detach(Observer observer);
void Notify();
string SubjectAction
{
get;
set;
}
}
Boss通知者具体类的实现:
class Boss : Subject
{
private string action;
private IList<Observer> observers = new List<Observer>();
//增加
public void Attach(Observer observer)
{
observers.Add(observer);
}
//减少
public void Detach(Observer observer)
{
observers.Remove(observer);
}
public string SubjectAction
{
get { return action; }
set { action = value; }
}
//通知
public void Notify()
{
foreach (Observer o in observers)
{
o.Update();
}
}
}
前台秘书类的实现与老板类似。
修改完之后,原来的抽象观察者中,把原来的前台秘书类修改成现在的抽象通知者就可以。这样的话不管是通知者还是观察者都是在面向抽象编程,这样的话就能很好的实现程序的可扩展性。
观察者模式又叫做发布-订阅模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己。
实现了这种面向抽象的编程,(从技术上说,由于里面的所有的方法的调用都是动态绑定的)Subject发出通知时并不需要知道谁是它的观察者,也就是说,具体的观察者是谁,它根本不需要知道。而任何一个具体的观察者也不需要知道其他观察者的存在。
那么什么时候适合用观察者模式呢:
当一个对象的改变需要同时改变其他对象,而且他不知道具体有多少对象有待改变,应该考虑使用观察者模式。
看前面的程序,会发现,前面的两个观察者的抽象中,使用到了抽象类,因为在这个例子中的两个具体的观察者,看股票的观察者和看NBA的观察者很类似,所以使用了抽象类,这里面多少有点继承的概念。
但是在现实的编程中,具体的观察者完全有可能风马牛不相及,他们他们都需要根据通知者的通知来做出一系列的动作,所以让他们都实现这样的一个接口还是不错的。
interface Observer
{
void Update();
}
观察者模式到这里就算是基本的结束了,但是我们可能会发现他的不足之处:
就拿我们的VS 2010 IDE来说,当我们点击运行按钮的时候,IDE中的好多控件会发生变化,这就是观察者模式,但是这些控件要么是.NET类库,要么是其他人事先写好的,它如何再能实现拥有Update的Observer接口呢?
上面的问题就是:观察者是别人实现的,它在实现的时候并没有把这个类看做一个观察者,所以就不会去弄一个抽象的观察者,现在怎么办?
事件委托实现
既然有些时候不能让观察者类实现一个抽象的观察者,那么就把观察者修改成这个样子,它在实现的时候并不是为观察者模式准备的:
class StockObserver
{
private string name;
private Subject sub;
public StockObserver(string name, Subject sub)
{
this.name = name;
this.sub = sub;
}
//关闭股票行情
public void CloseStockMarket()
{
Console.WriteLine("{0},{1}关闭股票行情,继续工作!", sub.SubjectAction, name);
}
}
class NBAObserver
{
private string name;
private Subject sub;
public NBAObserver(string name, Subject sub)
{
this.name = name;
this.sub = sub;
}
//关闭NBA直播
public void CloseNBADerectSeeding()
{
Console.WriteLine("{0},{1}关闭NBA直播,继续工作!", sub.SubjectAction, name);
}
}
抽象观察者不存在了,所以原来的通知者接口中不再有Attach和Detach:
interface Subject
{
void Notify();
string SubjectAction
{
get;
set;
}
}
既然通知者中没有了观察者的抽象了,但是在通知者的触发条件达到的时候,总的能调用观察者的相应的方法吧。所以利用委托类型的事件来保存这些观察者的函数指针。所以要先声明一个委托:
public delegate void EventHandler();
Boss类和前台秘书类:
class Boss : Subject
{
private string action;
//声明一个事件,它的类型为委托类型,事件说白了就是委托的引用变量
public event EventHandler Update;
public string SubjectAction
{
get { return action; }
set { action = value; }
}
//通知
public void Notify()
{
Update();
}
}
class Secretary : Subject
{
//与Boss类相似
}
这种委托事件的设计比观察者模式更加的巧妙,在观察者模式中,通知者和观察者都要持有对方的引用,不管是抽象的还是具体的,这个部分必须得有,但是上面的实现中通知者已经不再需要持有观察者的引用了。是不死进步了呢?但是观察者还持有通知者的引用,那是因为有参数需要从通知者传递到观察者。解决这个的办法就是我们利用事件传递参数,这样的话对方就都不用持有相互的引用了。
先定义事件的参数:
public class ObserverEventArgs : EventArgs
{
private string Message;
public ObserverEventArgs(string message)
{
Message = message;
}
public string ObserverMessage
{
get { return Message; }
set { Message = value; }
}
}
然后定义委托:
public delegate void EventHandler(object sender, EventArgs e);
注意这个委托是有两个参数的。所以对应的在观察者中的响应方法应该具有相同的函数签名。
先看观察者类的代码:
class StockObserver
{
private string name;
private Subject sub;
public StockObserver(string name)
{
this.name = name;
}
//关闭股票行情
public void CloseStockMarket(object sender, EventArgs e)
{
Console.WriteLine("{0},{1}关闭股票行情,继续工作!", ((ObserverEventArgs)e).ObserverMessage, name);
}
}
class NBAObserver
{
private string name;
public NBAObserver(string name)
{
this.name = name;
}
//关闭NBA直播
public void CloseNBADerectSeeding(object sender, EventArgs e)
{
Console.WriteLine("{0},{1}关闭NBA直播,继续工作!", ((ObserverEventArgs)e).ObserverMessage, name);
}
}
看到在观察者类中成功的去掉了通知者的对象的引用,两者的耦合度进一步的降低了,但是响应的函数要和委托事件具有相同的函数签名,这是委托调用在技术上的要求。
再看通知者的代码实现:
class Boss : Subject
{
ObserverEventArgs eventAgrs = new ObserverEventArgs("老板来了");
//声明一个事件,它的类型为委托类型,事件说白了就是委托的引用变量
public event EventHandler Update;
//通知
public void Notify()
{
Update(this, eventAgrs);
}
}
class Secretary : Subject
{
ObserverEventArgs eventAgrs = new ObserverEventArgs("老板来了");
public event EventHandler Update;
//通知
public void Notify()
{
Update(this, eventAgrs);
}//Notify()
}
最后是客户端的代码实现:
static void Main(string[] args)
{
//前台小姐
Secretary secretary = new Secretary();
//把观察者的处理方法绑定到通知者的事件中
secretary.Update += new EventHandler(new StockObserver("LiMing").CloseStockMarket);
secretary.Update += new EventHandler(new NBAObserver("XiaoLi").CloseNBADerectSeeding);
secretary.Notify();
Console.ReadKey();
}
通过委托事件,进一步修改了观察者模式,有关事件委托的具体的介绍,看下面的博客:
http://www.cnblogs.com/stemon/p/4433297.html
http://www.cnblogs.com/stemon/p/4431534.html
http://www.cnblogs.com/stemon/p/4212334.html