观察者模式是这样的场景里来的:
在一个公司里面的员工都很喜欢炒股票,甚至在上班的时候都会这么做。但是上班炒股票被老板发现是很严重的事情,所以他们就请前台秘书放哨,每当老板进来公司的时候,前台先打个电话通知他们,然后他们立刻关掉炒股票软件,认真工作。但是有一次,老板来的时候,前台秘书正好有事没在,所以老板直接进来了,把员工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