观察者模式

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

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

 

posted @ 2015-04-24 10:06  stemon  阅读(305)  评论(0编辑  收藏  举报