设计模式--观察者模式
今日看了《Head First》一书中关于“观察者”模式的讲解,突然间了解到自己对“观察者”的了解很是肤浅。今天采取与策略模式相同的方法来学习观察者模式。
1、什么是观察者模式?
观察者模式又叫做“发布-订阅”模式,它定义了对象之间的一对多依赖关系,这样当一个对象的状态改变时,他的所有依赖者都会收到通知并自动更新。这种情景在我们的生活中很常见,比如订阅报纸,关注某人的微博等,你订阅了(或者关注了)什么报纸(什么人),在这个报纸到达报社(这个人发了微博有了更新)时都会给你送到(都会通知你)。
这里所谓的“一”就是被观察者,我们习惯称它为“主题”(Subject),而“多”就是关心主题变化的一些“观察者”(Observer)了。主题是真正拥有数据的人,观察者是主题的依赖者,在数据变化时更新,这样比起让许多对象控制同一份数据来,设计要干净的多。
2、观察者模式的设计原则
2.1封装变化
在观察者模式中,会改变的是主题的状态,以及观察者的数目和类型。使用观察者模式,可以改变依赖依赖于主题状态的观察者对象,而不必改变主题,这就是提前规划。
2.2 针对接口编程,不针对实现编程
主题与观察者都使用接口,主题接口中提供注册观察者(registerObserver),删除观察者(removeObserver)和通知观察者(notifyObserver)的方法,继承该接口的主题应该实现这些方法。观察者接口提供更新(update)方法,所有继承自观察者接口的观察者类都应该实现这个方法。这样,观察者就能利用主题的接口向主题注册(registerObserver),而主题利用观察者接口通知观察者(update),这样能够让两者之间运作正常,又同时具有松耦合的特点。
2.3多用组合,少用继承”
在观察着模式中,主题需要维护一个观察者容器(数组),将许多观察者组合进主题中,对象之间的这种关系不是通过继承产生的,而是在运行时利用组合的方式产生的。
3、如何实现观察者模式 ?
根据以上的设计原则,实现观察者模式需要有两个接口,一个是被观察者接口(ISubject),提供注册观察者(registerObserver),删除观察者(removeObserver)和通知观察者(notifyObserver)的方法;另一个是观察者接口(IObserver),提供更新(update)方法。
3.1 那么如何将观察者注册呢?
在主题类(被观察者类,继承自主题接口的具体类)中应该有一个观察者容器,注册观察者就是将这个观察者添加到该容器。
3.2 那么被观察者如何送出通知呢?
被观察者类的数据在发生变化时,一般这个变化通过数据的setter方法检测,遍历观察者容器,让每一个观察者调用自己的update()方法更新自己的状态。
3.3 松耦合
关于观察者的一切,主题只需要知道观察者实现了某个接口(Observer的接口),它并不关心观察者具体是谁,做了什么东西和其他任何细节。并且任何时候我们都可以添加新的观察者,只要它实现了Observer的接口,并且当有新观察者出现时,主题代码不需要修改。我们可以独立的复用主题和观察者,因为二者并非紧耦合。
4、观察者模式类图
下面看一个简单观察者模式例子的类图(学生关注老师手机号码的变化)
5、学生关心老师电话号码变更的代码
//主题接口 interface Subject { void registerObserver(object o); void removeObserver(object o); void notifyObserver(); } //观察者接口 interface Observer { void update(object o); }
//被观察者--教师类 class Teacher:Subject { private string name; public string Name { get { return name; } set { name = value; } } private string phone; public string Phone { get { return phone; } set { phone = value; notifyObserver(); } } private ArrayList observers; public Teacher() { this.observers = new ArrayList(); } public void registerObserver(object o) { this.observers.Add(o); } public void removeObserver(object o) { this.observers.Remove(o); } public void notifyObserver() { for (int i = 0; i < observers.Count;i++ ) { ((Observer)observers[i]).update(Phone); } } } //观察者类--学生类 class Student:Observer { private string name; public string Name { get { return name; } set { name = value; } } private string Tphone; public Student(string aname) { this.Name = aname; } public void update(object o) { this.Tphone = (string)o; } public void show() { Console.WriteLine("Name:"+Name+"\nTeacher's Phone:"+Tphone); } }
//测试代码 static void Main(string[] args) { Teacher csl = new Teacher(); Student stu1 = new Student("Ann"); Student stu2 = new Student("Tom"); Student stu3 = new Student("Jhon"); csl.registerObserver(stu1); csl.registerObserver(stu2); csl.registerObserver(stu3); csl.Name = "CHENG SHAO LEI"; csl.Phone = "13333333333"; stu1.show(); stu2.show(); stu3.show(); csl.Phone = "12345678996"; stu3.show(); Console.ReadKey(); }
6、《Head First》中有一个非常好的观察者模式小例题--气象观测站,如下是程序的代码和类图
这个代码的实现和上一个程序的代码稍微有点区别,就是注册观察者的时机,第一个程序在客户使用时注册观察者,第二个程序则是在观察者出生时(构造器里)就制订了它的观察对象,这两中实现方法没有本质区别。
代码如下:
public interface Subject { void registerObserver(Observer o); void removeObserver(Observer o); void notifyObserver(); } public interface Observer { void update(float temp, float humidity, float pressure); }
public interface DisplayElement { void display(); }
public class ForecastDisplay : Observer, DisplayElement { private Subject weatherData; public ForecastDisplay(Subject weather) { this.weatherData = weather; weatherData.registerObserver(this); } public void update(float temp, float humidity, float pressure) { this.display(); } public void display() { Console.WriteLine("Improving weather on the way"); } } public class StatisticsDisplay : Observer, DisplayElement { private float maxTemp; private float minTemp; private float aveTemp; private Subject weatherData; public StatisticsDisplay(Subject weather) { this.weatherData = weather; weatherData.registerObserver(this); } public void update(float temp, float humidity, float pressure) { this.maxTemp = 80; this.minTemp = 80; this.aveTemp = 80; } public void display() { Console.WriteLine("Avg/Max/Min temperature = "+this.aveTemp+" / "+this.maxTemp+" / "+this.minTemp); } } public class CurrentConditionsDisplay : Observer, DisplayElement { private float temperater; private float humidity; private Subject weatherData; public CurrentConditionsDisplay(Subject weatherdata) { this.weatherData = weatherdata; weatherData.registerObserver(this); } public void update(float temp, float humidity, float pressure) { this.temperater = temp; this.humidity = humidity; display(); } public void display() { Console.WriteLine("Current conditions:"+temperater +" F degrees and "+humidity + "%humidity"); } }
public class WeatherData : Subject { private ArrayList obersers; private float temperature; private float humidity; private float pressure; public WeatherData() { this.obersers = new ArrayList(); } public void registerObserver(Observer o) { this.obersers.Add(o); } public void removeObserver(Observer o) { int i = this.obersers.IndexOf(o); if (i>=0) { this.obersers.Remove(o); } } public void notifyObserver() { for (int i = 0; i < this.obersers.Count;i++ ) { Observer observer = (Observer)this.obersers[i]; observer.update(temperature, humidity, pressure); } } public void measurementsChange() { this.notifyObserver(); } public void setMeasureMents(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChange(); } }
static void Main(string[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay current = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statistics = new StatisticsDisplay(weatherData); ForecastDisplay forecase = new ForecastDisplay(weatherData); weatherData.setMeasureMents(80,65,30.4f); Console.ReadKey(); }
7、关于JAVA内置的观察者模式
内置的不一定是好的!java.util.Observable也有“黑暗”的一面。Observable(可观察者类)是一个类,而不是一个“接口”,也就是说要使用他的方法我们只能继承他,而JAVA又不提供多继承,这很容易使程序员陷入两难境地。并且,这个类将关键的方法保护起来(protected),这样就更只能用继承了,这就违反了我们“多用组合,少用继承”的原则。