参考《大话设计模式》
1、 引入
生活中我们接收信息的渠道多种多样,太过庞杂的信息阅读一定程度上会带来信息垃圾,而选择自己喜欢、感兴趣的内容订阅它,当它状态更新(发布)时通知我们,我们再去查阅,既减少了等待消息的成本,也能及时接收最新消息,这种情形就是观察者模式的典型应用。因此,观察者模式经常用于具备以下特征的场景中:
(1)对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变;
(2)对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。
2. 定义
观察者模式,有时也被称为发布——订阅(publish—subscribe)模式、模型——视图(Model-View)模式、源-收听者(Listener)模式或从属模式,是软件设计模式的一种。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象状态发生变化时,通知所有观察者对象,令它们自动更新状态。该模式通常被用来实现事件处理系统。
观察者模式通常涉及的角色有4个:
- 抽象主题:把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
- 具体主题:将有关状态存入具体观察者对象;在具体主题内部装填改变时,给所有登记过的观察者发出通知;
- 抽象观察者:为所有的具体观察者定义一个接口,在得到主题通知时更新自己;
- 具体观察者:实现抽象观察者角色要求的更新接口,以便使本身的状态与主题状态协调。
3. 场景模拟及代码实现
场景模拟:一个办公室内员工趁老板不在查看股票详情,为了防止被老板看到,前台小王在看到老板来到公司时会打电话通知其中一个人,这时大家都会知道老板回来并关闭相应的页面进入工作状态。
该场景中老板为“被观察者”,各个看股票的员工为“观察者”,前台小王为“具体主题”,当被观察者(老板)的状态(回来/没回来)发生改变时,由具体主题(前台小王)给登记过的观察者(看股票的员工)发出通知,使得观察者行为自动发生改变(关闭网页)。
场景分析:分析该情景,包括3个类,老板,小王,员工。其中类之间的关系为,小王与员工为1~多关联,该情景涉及的方法包括,前台发布消息、观察者接收通知。
3.1 初步实现
public class StockObserver { private Scenery scenery; private string name; public StockObserver(string name,Scenery scenery) { this.name = name; this.scenery = scenery; } public void Update() { Console.WriteLine("{0}{1}关闭股票行情,继续工作!",scenery.SceneryAction,name); } }
/// <summary> /// 前台小王 /// </summary> public class Scenery { //同事列表 private List<StockObserver> stockObserverList = new List<StockObserver>(); private string action; //增加同事 public void Attach(StockObserver stockObserve) { stockObserverList.Add(stockObserve); } //通知 public void Notify() { foreach(var item in stockObserverList) { item.Update(); } } //前天状态 public string SceneryAction { get { return action; } set { action = value; } } }
客户端实现:
static void Main(string[] args) { Scenery qiantai = new Scenery(); StockObserver so1 = new StockObserver("zs", qiantai); StockObserver so2 = new StockObserver("ls", qiantai); //前台添加需要通知的人 qiantai.Attach(so1); qiantai.Attach(so2); //老板回来,前台通知,通知更新工作状态 qiantai.Notify(); Console.WriteLine("Hello World!"); }
上述代码简单的实现了场景中描述的过程,但是Scenery与StockObserver相互关联,成紧耦合的状态,不符合设计模式中的依赖倒转原则。与第2节中对应,相当于只有具体主题和具体观察者。此时,如果公司看NBA的员工也想让前台小王在老板回来时通知,除了增加NbaObserver之外,客户端也需要增加具体的NbaObserver对象,并添加到主题通知列表中。
3.2 代码优化——解耦1
建立一个观察者的抽象接口 ,令其他观察者(看股票的同事、看NBA的同事)都继承它。而前台通知时,与抽象的观察者接口进行关联,实现与具体类解耦。
抽象观察者和具体观察者类:
/// <summary> /// 抽象观察者 /// </summary> public abstract class Observer { public string name; public Scenery scenery; public Observer(string name, Scenery scenery) { this.name = name; this.scenery = scenery; } public abstract void Update(); } /// <summary> /// 具体观察者——看股票的同事 /// </summary> public class StockObserver:Observer { public StockObserver(string name, Scenery scenery) : base(name, scenery) { } public override void Update() { Console.WriteLine("{0}{1}关闭股票页面,开始工作", scenery.SceneryAction, name); } } /// <summary> /// 具体观察者——看NBA的同事 /// </summary> public class NBAObserver : Observer { public NBAObserver(string name, Scenery scenery) : base(name, scenery) { } public override void Update() { Console.WriteLine("{0}{1}关闭NBA页面,开始工作", scenery.SceneryAction, name); } }
/// <summary> /// 前台小王 /// </summary> public class Scenery { //同事列表 private List<Observer> observerList=new List<Observer>(); private string action; //增加同事 public void Attach(Observer observer) { observerList.Add(observer); } //减少同事 public void Detach(Observer observer) { observerList.Remove(observer); } //通知 public void Notify() { foreach(var item in observerList) { item.Update(); } } //前天状态 public string SceneryAction { get { return action; } set { action = value; } } }
static void Main(string[] args) { Scenery sn = new Scenery(); Observer observerStock = new StockObserver("zs",sn); Observer observerNBA = new NBAObserver("ls",sn); sn.Attach(observerStock); sn.Attach(observerNBA); sn.SceneryAction = "老板回来了"; sn.Notify(); Console.WriteLine("Hello World!"); }
然而,上述代码实现中,虽然增加了抽象的观察者接口,但是前台Scenery是一个具体类,与Observer进行耦合,因此,需要对前台具体类进行扩展,以满足,依赖于抽象的原则。
3.3 代码优化——解耦2
在3.2的基础上,假设通知观察者的前台换了一个人,或者老板回来后会通知员工做某件事,此时,需要对前台主题进行扩展,其他具体主题继承它。实现如下:
主题:
public interface Subject { /// <summary> /// 添加观察者. /// </summary> /// <param name="observer"></param> void Attach(Observer observer); /// <summary> /// 移除同事 /// </summary> /// <param name="observer"></param> void Detach(Observer observer) ; /// <summary> /// 通知. /// </summary> void Notify(); /// <summary> /// 前台动作 /// </summary> string Action { get; set; } } /// <summary> /// 具体主题——前台小王 /// </summary> public class Scenery: Subject { //与观察者接口耦合 public List<Observer> observeList = new List<Observer>(); public string action; public void Attach(Observer observer) { observeList.Add(observer); } public void Detach(Observer observer) { observeList.Remove(observer); } public void Notify() { foreach (var item in observeList) { item.Update(); } } public string Action { get { return action; } set { action = value; } } } /// <summary> /// 具体主题——老板/前台小周 /// </summary> public class Boss : Subject { //与观察者接口耦合 public List<Observer> observeList = new List<Observer>(); public string action; public void Attach(Observer observer) { observeList.Add(observer); } public void Detach(Observer observer) { observeList.Remove(observer); } public void Notify() { foreach (var item in observeList) { item.Update(); } } public string Action { get { return action; } set { action = value; } } }
观察者:
/// <summary> /// 抽象观察者 /// </summary> public abstract class Observer { public string name; public Subject subject; public Observer(string name, Subject subject) { this.name = name; this.subject = subject; } public abstract void Update(); } /// <summary> /// 具体观察者——看股票的同事 /// </summary> public class StockObserver:Observer { public StockObserver(string name, Subject subject) : base(name, subject) { } public override void Update() { Console.WriteLine("{0}{1}关闭股票页面,开始工作", subject.Action, name); } } /// <summary> /// 具体观察者——看NBA的同事 /// </summary> public class NBAObserver : Observer { public NBAObserver(string name, Subject subject) : base(name, subject) { } public override void Update() { Console.WriteLine("{0}{1}关闭股票页面,开始工作", subject.Action, name); } }
客户端调用:
static void Main(string[] args) { Subject wang = new Scenery(); Subject boss = new Boss(); Observer ob1 = new StockObserver("zs",wang); Observer ob2 = new NBAObserver("ls",boss); wang.Attach(ob1); boss.Attach(ob2); wang.Action = "前台通知,老板回来了"; boss.Action = "我是老板,我回来了"; wang.Notify(); boss.Notify(); Console.WriteLine("Hello World!"); }
运行结果:
4. 观察者模式的UML图
对上述第3节中场景涉及的类,画UML图,如下:
其中,抽象观察者模式中抽象观察者角色,股票观察者和逛淘宝观察者对应具体观察者角色,抽象统治者对应抽象主题角色,前台和其他通知者对应具体主题角色。观察者模式的UML图如下:
5. 总结
观察者模式的主要优点是实现了表示层与逻辑层的分离,在观察目标和观察者之间是抽象耦合,支持广播通信,缺点是,观察者和被观察者之间依然存在耦合,如果没有抽象观察者的接口,通知的功能将无法完成,且现实中每个具体观察者并非调用同一个方法“update”。