观察者模式
双向耦合的代码
public class Secretary { private IList<StockObserver> observers = new List<StockObserver>(); private string action; //增加 public void Attach(StockObserver observer) { observers.Add(observer); } //通知 public void Notify() { foreach (StockObserver item in observers) { item.Update(); } } //前台状态 public string SecretaryAction { get { return action; } set { action = value; } } }
public 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); } }
private void btn_observersUser_Click(object sender, EventArgs e) { //前台小姐,通知自己 Secretary tongzizhe = new Secretary(); //看股票的同事 StockObserver tongshi1 = new StockObserver("同事1", tongzizhe); StockObserver tongshi2 = new StockObserver("同事2", tongzizhe); //前台记下了两位同事 tongzizhe.Attach(tongshi1); tongzizhe.Attach(tongshi2); //发现老板回来 tongzizhe.SecretaryAction = "老板回来了"; //通知两个同事 tongzizhe.Notify(); }
这样会存在问题,前台类与看股票类存在耦合,前台需要观察者,观察者网需要前台的状态。
如果观察者当中还有人是想看NBA的网上直播(由于时差关系,美国NBA篮球比赛通常都是在北京时间的上午开始),你的‘前台’类代码怎么办?“那就得改动了。”
发现这个问题了,你说该怎么办?想想我们的设计原则?
“我就知道,你又要提醒我了。首先开放封闭原则,修改原有代码就说明设计不够好。其次是依赖倒转原则,我们应该让程序都依赖抽象,而不是互相依赖。
解耦实践1:
增加了抽象的观察者,又增加了两个具体的观察者
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 { 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) { } public override void Update() { Console.WriteLine("{0}{1}关闭NBA直播,继续工作!", sub.SecretaryAction, name); } }
让两个观察者去继承“抽象观察者”,对于“Update(更新)"方法做重写操作。
下面是前台秘书类的编写,把所有的具体观察者耦合的地方都改成了”抽象观察者“
class Secretary { private IList<Observer> observers = new List<Observer>(); private string action; //增加 public void Attach(Observer observer) { observers.Add(observer); } public void Detach(Observer observer) { observers.Remove(observer); } //通知 public void Notify() { foreach (StockObserver item in observers) { item.Update(); } } //前台状态 public string SecretaryAction { get { return action; } set { action = value; } } }
但是这又存在一个问题,想一下,在具体观察者中有么有与具体的类耦合的?
前台秘书类又是一个具体的类,也应该抽象出来。
“对呀,你想想看,你们公司最后- -次,你们的老板回来,前台来不及电话了,于是通知大家的任务变成谁来做?”
“是老板,对的,其实老板也好,前台也好,都是具体的通知者,这里观察者也不应该依赖具体的实现,而是一个抽象的通知者。”
“另外,就算是你们的前台,如果某 一个同事和她有矛盾,她生气了,于是不再通知这位同事,此时,她是否应该把这个对象从她加入的观察者列表中删除?”
那么调用Detach就好了
解耦实践2:
interface Subject { void Attach(Observer observer); void Detach(Observer observer); void Notify(); string SubjectState { get; set; } }
class Boss : Subject { private IList<Observer> observers = new List<Observer>(); private string action; //增加 public void Attach(Observer observer) { observers.Add(observer); } public void Detach(Observer observer) { observers.Remove(observer); } //通知 public void Notify() { foreach (Observer item in observers) { item.Update(); } } //前台状态 public string SubjectState { get { return action; } set { action = value; } } }
abstract class Observer { protected string name; protected Subject sub; public Observer(string name, Subject sub) { this.name = name; this.sub = sub; } public abstract void Update(); }
class StockObserver:Observer //没有public { public StockObserver(string name, Subject sub):base(name,sub) { } public override void Update() { Console.WriteLine("{0}{1}关闭股票行情,继续工作!", sub.SubjectState, name); } }
private void btn_observersUser_Click(object sender, EventArgs e) { //老板,通知自己 Boss hahahah = new Boss(); //看股票的同事 StockObserver tongshi1 = new StockObserver("同事1", hahahah); NBAObserver tongshi2 = new NBAObserver("同事2", hahahah); //前台记下了两位同事 hahahah.Attach(tongshi1); hahahah.Attach(tongshi2); hahahah.Detach(tongshi1); //发现老板回来 hahahah.SubjectState = "老板我回来了"; //通知两个同事 hahahah.Notify(); }
所以啥叫观察者模式呢?
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己。
观察者模式特点:
用观察者模式的动机是啥?
将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便[DP]。
而观察者模式的关键对象是主题Subject和观察者Observer, 一个Subject可以有任意数目的依赖它的Observer,一旦 Subject的状态发生了改变,所有的Observer都可以得到通知。
Subject 发出通知时并不需要知道谁是它的观察者,也就是说,具体观察者是谁,它根本不需要知道。而任何一个具体观察者不知道也不需要知道其他观察者的存在。”
“什么时候考虑使用观察者模式呢?”
不知道具体有多少对象有待改变时,应该考虑使用观察者模式。还有吗?”“我感觉当一个抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。”
总的来讲,观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。”
这实在是依赖倒转原则的最佳体现呀。
注意:在程序中抽象观察者用的是抽象类而不是接口,是因为看股票的同事和NBA的同事是相似的,所以用了抽象类,这样可以共用一些代码,而接口是方法的实现,没有太大意义。
但是也可以用接口,因为在现实的编程中,具体的观察者可能是风马牛不相关的类,但是他们都需要通知者的通知来做出Uodate()操作,所以都实现这样一个接口就可以了。
但是观察者模式也有不足,就是抽象通知者还是依赖抽象观察者,也就是说万一没有了抽象观察者这样的接口,那么这样的通知功能就不能完成。
所以如果通知者和观察者之间互相不知道,由客户端来决定就好了。
这里可以用委托!
事件委托的实现:
首先,既然抽象通知者和抽象观察者是依赖关系,而且将由客户端来决定通知谁,那么就去掉抽象观察者!
其次【这里,将更新方法改变,改成各自的更新方法】
class StockObserver { private string name; private Subject sub; public StockObserver(string name, Subject sub) { this.name = name; this.sub = sub; } public void CloseStockUpdate() { Console.WriteLine("{0}{1}关闭股票行情,继续工作!", sub.SubjectState, name); } }
class NBAObserver { private string name; private Subject sub; public NBAObserver(string name, Subject sub) { this.name = name; this.sub = sub; } public void CloseNBAUpdate() { Console.WriteLine("{0}{1}关闭NBA直播,继续工作!", sub.SubjectState, name); } }
现实就是这样,更新方法本就不一定相同
interface Subject { //void Attach(Observer observer); //void Detach(Observer observer); void Notify(); string SubjectState { get; set; } }
因为抽象通知者不依赖抽象观察者,所以增加和减少方法就不必了!
然后是老板和前台的处理:
delegate void EventHander(); class Boss : Subject { public event EventHander Update; //private IList<Observer> observers = new List<Observer>(); private string action; //增加 //public void Attach(Observer observer) //{ // observers.Add(observer); //} //public void Detach(Observer observer) //{ // observers.Remove(observer); //} //通知 public void Notify() { //foreach (Observer item in observers) //{ // item.Update(); //} Update(); } //前台状态 public string SubjectState { get { return action; } set { action = value; } } }
然后是客户端代码,由此来决定来通知谁
private void btn_observersUser_Click(object sender, EventArgs e) { //老板,通知自己 Boss hahahah = new Boss(); //看股票的同事 StockObserver tongshi1 = new StockObserver("同事1", hahahah); NBAObserver tongshi2 = new NBAObserver("同事2", hahahah); //前台记下了两位同事 hahahah.Update += new EventHander(tongshi1.CloseStockUpdate); hahahah.Update += new EventHander(tongshi2.CloseNBAUpdate); //发现老板回来 hahahah.SubjectState = "老板我回来了"; //通知两个同事 hahahah.Notify(); }