设计模式:观察者模式(一)
- 观察者模式定义了对象之间一个主题对多应多个观察者的依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
- 角色:
- 主题(Subject):主题是一个接口,该接口规定了具体主题需要实现的方法
- 观察者(Observer):观察者是一个接口,该接口规定了具体观察者用来更新数据的方法。
- 具体主题(ConcreteSubject):具体主题是实现主题接口类的一个实例,该实例包含有可以经常发生变化的数据。具体主题使用一个集合,比如ArrayList,存放观察者的引用,以便数据变化时通知具体的观察者。
- 具体观察者(Concrete Observer):具体观察者是实现观察者接口的一个实例。具体观察者包含有可以存放具体主题引用的主题接口变量,以便具体观察者让具体主题将自己添加到具体主题的集合中,使自己成为它的观察者,或让这个具体的主题将自己从具体的主题中的观察者列表中删除,使自己不再是它的观察者。
- 一对多关系:
- 利用观察者模式,主题是具有状态的对象,并可以控制这些状态。也就是说,有“一个”具有状态的主题。另一方面,观察者使用这些状态,虽然这些状态并不属于他们。有许多的观察者,依赖主题来告诉他们状态何时改变了。这就产生一个关系:“一个”主题对应“多个”观察者的关系。
- Demo背景
- 设计一个气象观测站,测量温度、湿度、气压等,会有多种公告板如气温布告板,舒适度布告板,天气预报布告板等等。每当天气数据变化时,这些布告板的数据就需要相应自动更新。
- 代码实现
- 主题接口:ISubject
public interface ISubject { void RegisterObserver(IObserver o); void RemoveObserver(IObserver o); void NotifyObserver(); }
- 观察者接口:IObserver
public interface IObserver { void Update(float temp, float humidity, float pressure); }
- 具体主题:WeatherData
public class WeatherData:ISubject { private ArrayList observers; private float temperature; private float humidity; private float pressure; public WeatherData() { observers = new ArrayList(); } public void RegisterObserver(IObserver o) { observers.Add(o); } public void RemoveObserver(IObserver o) { int i = observers.IndexOf(o); if (i >= 0) { observers.RemoveAt(i); } } public void NotifyObserver() { for (int i = 0; i < observers.Count; i++) { IObserver observer = (IObserver)observers[i]; observer.Update(temperature, humidity, pressure); } } public void messurementsChanged() { NotifyObserver(); } public void SetMessureMents(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; messurementsChanged(); } }
- 具体观察者:CurrentConditionDisplay,ForecastDisplay
public interface IDisplayElement { void Display(); }
public class CurrentConditionDisplay : IObserver, IDisplayElement { private float temperature; private float humidity; private float pressure; private ISubject weatherData; public void Display() { Console.WriteLine($"公告板1 当前天气 => temperature:{temperature},humidity:{humidity},pressure:{pressure}"); } public void Update(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; Display(); } public CurrentConditionDisplay(ISubject weatherData) { this.weatherData = weatherData; weatherData.RegisterObserver(this); } }
public class ForecastDisplay : IObserver, IDisplayElement { private float temperature; private float humidity; private float pressure; private ISubject weatherData; public void Display() { Console.WriteLine($"公告板2 明天天气 => temperature:{temperature},humidity:{humidity},pressure:{pressure}"); } public void Update(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; Display(); } public ForecastDisplay(ISubject weatherData) { this.weatherData = weatherData; weatherData.RegisterObserver(this); } }
- 主题接口:ISubject
-
- 测试代码与运行结果
static void Main(string[] args) { WeatherData weatherData = new WeatherData(); Console.WriteLine("--------公告板1加入观察者-------"); CurrentConditionDisplay display1 = new CurrentConditionDisplay(weatherData); weatherData.SetMessureMents(10, 20, 30); Console.WriteLine("--------公告板2加入观察者-------"); ForecastDisplay display2 = new ForecastDisplay(weatherData); weatherData.SetMessureMents(15, 25, 35); Console.WriteLine("--------公告板1退出观察者-------"); weatherData.RemoveObserver(display1); weatherData.SetMessureMents(19, 29, 39); }
- 测试代码与运行结果
- 优点:
- 具体主题和具体观察者是松耦合关系。由于主题(Subject)接口仅仅依赖于观察者(Observer)接口,因此具体主题只是知道它的观察者是实现观察者(Observer)接口的某个类的实例,但不需要知道具体是哪个类。同样,由于观察者仅仅依赖于主题(Subject)接口,因此具体观察者只是知道它依赖的主题是实现主题(subject)接口的某个类的实例,但不需要知道具体是哪个类。
- 观察模式满足“开-闭原则”。主题(Subject)接口仅仅依赖于观察者(Observer)接口,这样,我们就可以让创建具体主题的类也仅仅是依赖于观察者(Observer)接口,因此如果增加新的实现观察者(Observer)接口的类,不必修改创建具体主题的类的代码。同样,创建具体观察者的类仅仅依赖于主题(Observer)接口,如果增加新的实现主题(Subject)接口的类,也不必修改创建具体观察者类的代码。
- 源码地址: https://github.com/DonyGu/DesignPatterns