[Head First设计模式]山西面馆中的设计模式——装饰者模式
不知不自觉又将设计模式融入生活了,吃个饭也不得安生,也发现生活中的很多场景,都可以用设计模式来模拟。原来设计模式就在我身边。
为什么观察者模式会出现呢?
为了建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是为什么需要观察者模式。
观察者模式(Observer Pattern):定义了对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又被称为发布-订阅(Publish/Subscribe)模式,模型-视图(Model/View)模式,源-监听器(Source/Listener)模式,或从属者(Dependents)模式。观察者模式是一种对象行为型的模式。
当两个对象是松耦合的,它们之间能够交互,但是相互了解的很少。
观察者模式提供了主题和观察者之间的松耦合设计。因为主题只知道观察者实现了某个接口(即Observer接口)。主题不需要知道具体观察者是谁,做了些什么或其它任何细节。要增加新的观察者或删除观察者,主题不会受到任何影响,不必修改主题代码。
可以独立地复用主题和观察者,他们之间互不影响,即是松耦合。
设计原则:在交互的对象之间争取松耦合设计
由于松耦合设计使得对象间的依赖最小化,所以,我们能够创建柔性的oo系统,应对变化的情况,因为对象间的依赖降到了最低。
书中的气象站例子:
代码实现:
1 public interface Subject 2 { 3 void RegisterObserver(Observer o);//这两个方法都需要一个观察者作为参数,该观察者是用来注册或被删除的。 4 void RemoveObserver(Observer o); 5 void NotifyObservers();//当主题改变状态时,这个方法会被调用,以通知所有的观察者 6 }
1 public interface Observer 2 { 3 /// <summary> 4 /// 当气象观测值改变时,主题会把这些状态值作为方法的参数传给观察者 5 /// </summary> 6 /// <param name="temp"></param> 7 /// <param name="humidity"></param> 8 /// <param name="pressure"></param> 9 void Update(float temp,float humidity,float pressure); 10 }
1 /// <summary> 2 /// 该接口之包含一个方法,也就是display方法,当布告板需要显示时,调用次方法。 3 /// </summary> 4 public interface DisplayElement 5 { 6 void Display(); 7 }
在WeatherData中实现主题接口
1 /// <summary> 2 /// WeatherData实现了subject接口 3 /// </summary> 4 public class WeatherData : Subject 5 { 6 /// <summary> 7 /// 我们加上一个ArrayList来记录观察者,此ArrayList是在构造器中建立的 8 /// </summary> 9 private ArrayList observers; 10 private float temperature; 11 private float humidity; 12 private float pressure; 13 public WeatherData() 14 { 15 observers = new ArrayList(); 16 } 17 public void RegisterObserver(Observer o) 18 { 19 //当注册观察者的时候,秩序把他们加在ArrayList后面就行了 20 observers.Add(o); 21 } 22 23 public void RemoveObserver(Observer o) 24 { 25 //同样,当观察者想取消注册,只需要移除 26 int i = observers.IndexOf(o); 27 if (i > 0) 28 { 29 observers.Remove(i); 30 } 31 } 32 /// <summary> 33 /// 有趣的地方来了,在这里,我们把状态告诉每一个观察者, 34 /// 因为观察者都实现了Update方法,所以我们知道如何通知他们 35 /// </summary> 36 public void NotifyObservers() 37 { 38 for (int i = 0; i < observers.Count; i++) 39 { 40 Observer observer = (Observer)observers[i]; 41 observer.Update(temperature, humidity, pressure); 42 } 43 } 44 /// <summary> 45 /// 当气象站得到更新观测值的时,我们通知观察者。 46 /// </summary> 47 public void MeasurementChanged() { 48 NotifyObservers(); 49 } 50 public void SetMeasurements(float temperature, float humidity, float pressure) 51 { 52 this.temperature = temperature; 53 this.humidity = humidity; 54 this.pressure = pressure; 55 } 56 }
建立布告板
1 /// <summary> 2 /// 此布告板实现了Observer接口,所以可以从weatherdata对象中获得改变, 3 /// 同时也实现了DisplayElement接口,因为我们的API规定所有的布告板必须实现此接口 4 /// </summary> 5 public class CurrentConditionsDisplay:Observer,DisplayElement 6 { 7 private float temperature; 8 private float humidity; 9 10 private Subject weatherData; 11 /// <summary> 12 /// 构造器需要weatherdata对象作为注册用 13 /// </summary> 14 /// <param name="weatherData"></param> 15 public CurrentConditionsDisplay(Subject weatherData) 16 { 17 this.weatherData = weatherData; 18 weatherData.RegisterObserver(this); 19 } 20 public void Update(float temp, float humidity, float pressure) 21 { 22 //当update被调用的时候,我们把温度和湿度保存起来,然后调用Display(); 23 this.temperature = temp; 24 this.humidity = humidity; 25 Display(); 26 27 28 } 29 /// <summary> 30 /// 只是将最近的湿度和温度显示出来。 31 /// </summary> 32 public void Display() 33 { 34 Console.WriteLine("Current conditions:"+temperature+" F degrees and"+humidity+"% humidity"); 35 } 36 }
测试代码
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //首先创建weatherData对象 6 WeatherData weatherData = new WeatherData(); 7 CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); 8 weatherData.SetMeasurements(80, 65, 30.4f); 9 //通知 10 weatherData.NotifyObservers(); 11 weatherData.SetMeasurements(82, 70, 29.2f); 12 weatherData.NotifyObservers(); 13 weatherData.SetMeasurements(78, 90, 29.2f); 14 weatherData.NotifyObservers(); 15 Console.Read(); 16 } 17 }
结果:
场景:山西面馆中,我点餐,服务员传话给厨师,厨师应答,厨师做饭。
分析:在这个场景中,我:被观察者,服务员,厨师:观察者。
我:“一份西红柿鸡蛋汤面”
一系列动作
服务员:1.向厨师传话“一份西红柿鸡蛋汤面”
厨师:1.收到,2.开始做。
在.NET中,C#使用委托以及事件,可以很好的实现观察者模式。委托相当于“订阅清单”的角色,当目标中关联了该委托的事件被触发时,则委托将自动按序执行观察者注册于委托中的方法。
代码实现:
基类的实现
1 /// <summary> 2 /// 自定义事件参数 3 /// </summary> 4 public class EatSomthingEventArgs : EventArgs 5 { 6 private string foodName; 7 public EatSomthingEventArgs(string foodName) 8 { 9 this.foodName = foodName; 10 } 11 public string FoodName 12 { 13 get { return foodName; } 14 set { foodName = value; } 15 }
1 /// <summary> 2 /// 声明一个委托,用于代理一系列自定义方法 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 public delegate void EatSomethingEventHandler(object sender, EatSomthingEventArgs e);
1 /// <summary> 2 /// 在Observer Pattern(观察者模式)中,此类作为所有Subject(主题)的抽象基类 3 /// 此抽象类无抽象方法,主要是为了不能实例化该类对象,确保模式完整性. 4 /// 具体的主题(比如:customer)就继承自该类,也可以说是被观察者的基类。 5 /// </summary> 6 public abstract class Subject 7 { 8 public event EatSomethingEventHandler EatSomethingHandler; 9 public string FoodName { set; get; } 10 /// <summary> 11 /// 封装了触发事件的方法 12 /// 主要为了规范化及安全性,除观察者基类外,其派生类不直接触发委托事件 13 /// </summary> 14 protected void Notify() 15 { 16 if (EatSomethingHandler != null) 17 { 18 EatSomethingHandler(this, new EatSomthingEventArgs(this.FoodName)); 19 } 20 } 21 }
1 /// <summary> 2 /// 此类作为所有Observer(观察者)的抽象基类 3 /// 此类作为观察者基类,用于规划所有观察者(即订阅方)订阅行为 4 /// 具体实施过程: 5 /// 1.指定观察者所观察的对象(即发布方).(通过构造器传递) 6 /// 2.规划观察者自身需要作出响应方法列表 7 /// 3.注册需要委托执行的方法.(通过构造器实现) 8 /// </summary> 9 public abstract class Observer 10 { 11 /// <summary> 12 /// 构造时通过传入具体主题subject,把观察者与模型关联,并完成订阅. 13 /// </summary> 14 /// <param name="subject"></param> 15 public Observer(Subject subject) 16 { 17 subject.EatSomethingHandler += new EatSomethingEventHandler(Response); 18 } 19 /// <summary> 20 /// 规划了观察者的一种行为(方法),所有派生于该观察者基类的具体观察者都 21 /// 通过覆盖该方法来实现作出响应的行为. 22 /// </summary> 23 /// <param name="sender"></param> 24 /// <param name="e"></param> 25 public abstract void Response(object sender, EatSomthingEventArgs e); 26 }
1 /// <summary> 2 /// 另一个观察者基类.该观察者类型拥有两个响应行为 3 /// </summary> 4 public abstract class Observer2 5 {/// <summary> 6 /// 构造时通过传入具体主题subject,把观察者与模型关联,并完成订阅. 7 /// </summary> 8 /// <param name="subject"></param> 9 public Observer2(Subject subject) 10 { 11 subject.EatSomethingHandler += new EatSomethingEventHandler(Response); 12 subject.EatSomethingHandler += new EatSomethingEventHandler(Response2); 13 } 14 /// <summary> 15 /// 规划了观察者的一种行为(方法),所有派生于该观察者基类的具体观察者都 16 /// 通过覆盖该方法来实现作出响应的行为. 17 /// </summary> 18 /// <param name="sender"></param> 19 /// <param name="e"></param> 20 public abstract void Response(object sender, EatSomthingEventArgs e); 21 public abstract void Response2(object sender, EatSomthingEventArgs e); 22 }
观察者实现
1 /// <summary> 2 /// 厨师类 继承自抽象观察者基类Observer2 两个反应 3 /// </summary> 4 public class Cook : Observer2 5 { 6 public Cook(Subject subject) 7 : base(subject) 8 { } 9 public override void Response(object sender, EatSomthingEventArgs e) 10 { 11 Console.WriteLine("厨师:收到了,要做一份{0}", e.FoodName); 12 } 13 14 public override void Response2(object sender, EatSomthingEventArgs e) 15 { 16 Console.WriteLine("厨师:{0}已经在做了,稍等.....", e.FoodName); 17 } 18 }
1 /// <summary> 2 /// 具体的观察者 服务员 继承自观察者基类 3 /// </summary> 4 public class Waiter : Observer 5 { 6 public Waiter(Subject subject) 7 : base(subject) 8 { } 9 public override void Response(object sender, EatSomthingEventArgs e) 10 { 11 Console.WriteLine("美女服务员:康师傅做一份{0}", e.FoodName); 12 } 13 }
被观察者
1 /// <summary> 2 /// 具体的主题类 即被观察者 3 /// </summary> 4 public class Customer : Subject 5 { 6 public Customer(string foodName) 7 { 8 base.FoodName = foodName; 9 } 10 /// <summary> 11 /// 订餐方法 此方法将触发观察者的一系列动作 12 /// </summary> 13 public void OrderMeal() 14 { 15 Console.WriteLine("顾客:我要一份{0}", base.FoodName); 16 base.Notify(); 17 } 18 }
测试:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Customer customer = new Customer("西红柿鸡蛋汤面"); 6 Waiter waiter = new Waiter(customer); 7 Cook cook = new Cook(customer); 8 //订餐 动作 9 customer.OrderMeal(); 10 Console.Read(); 11 } 12 }
当一个对象的数据更新时需要通知其他对象,但这个对象又不希望和被通知的那些对象形成紧耦合。
当一个对象的数据更新时,这个对象需要让其他对象也各自更新自己的数据,但这个对象不知道具体有多少对象需要更新数据
观察者模式与备忘录模式的关系
观察者模式使用了备忘录模式(Memento Pattern),暂时将观察者对象存储在被观察者对象里面
观察者模式与MVC模式的关系
观察者模式可以用来实现MVC模式。观察者模式中的主题便是MVC模式中的模型加控制器,而观察者便是视图
一般情况下,MVC是观察者模式、组合模式、策略模式等设计模式的组合。
观察者模式的有点
1,具体主题和具体观察者是松耦合关系。
由于主题(Subject)接口仅仅依赖于观察者(Observer)接口,因此具体主题只是知道它的观察者是实现观察者(Observer)接口的某个类的实例,但不需要知道具体是哪个类。同样,由于观察者仅仅依赖于主题(Subject)接口,因此具体观察者只是知道它依赖的主题是实现主题(subject)接口的某个类的实例,但不需要知道具体是哪个类。
2,观察者模式满足“开-闭原则”。
主题(Subject)接口仅仅依赖于观察者(Observer)接口,这样,我们就可以让创建具体主题的类也仅仅是依赖于观察者(Observer)接口,因此如果增加新的实现观察者(Observer)接口的类,不必修改创建具体主题的类的代码。同样,创建具体观察者的类仅仅依赖于主题(Observer)接口,如果增加新的实现主题(Subject)接口的类,也不必修改创建具体观察者类的代码。
观察者模式的缺点
参考书
Head First 设计模式