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

  • 相关阅读:
    洛谷 P1508 Likecloud-吃、吃、吃
    Codevs 1158 尼克的任务
    2017.10.6 国庆清北 D6T2 同余方程组
    2017.10.6 国庆清北 D6T1 排序
    2017.10.3 国庆清北 D3T3 解迷游戏
    2017.10.3 国庆清北 D3T2 公交车
    2017.10.3 国庆清北 D3T1 括号序列
    2017.10.4 国庆清北 D4T1 财富
    2017.10.7 国庆清北 D7T2 第k大区间
    2017.10.7 国庆清北 D7T1 计数
  • 原文地址:https://www.cnblogs.com/stemon/p/4452622.html
Copyright © 2011-2022 走看看