zoukankan      html  css  js  c++  java
  • 观察者模式

    观察者模式是这样的场景里来的:

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

  • 相关阅读:
    自主学习之RxSwift(一) -----Driver
    RxSwift:ReactiveX for Swift 翻译
    C简单实现动态顺序表
    C简单实现双向链表
    C实现单链表
    享受这一点点的平淡
    C文件读写
    大神都在看的RxSwift 的完全入坑手册
    字符串常量存在何处
    认识自己
  • 原文地址:https://www.cnblogs.com/stemon/p/4452622.html
Copyright © 2011-2022 走看看