设计模式系列之三:观察者模式(Observer Pattern)

rel="File-List" href="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_filelist.xml" />

观察者模式

一、引子气象站监测应用的概况

系统分三个部分

气象站:获取实际气象数据的物理装置

WeatherData对象:追踪来自气象站的数据,并更新布告板。

布告板:显示目前天气状况给用户。包括目前状况、气象统计、天气预报。

这个项目要完成的任务是:利用WeatherData对象取得数据,并更新三个布告板。

二、WeatherData

rel="File-List" href="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_filelist.xml" />

Class WeatherData

{

  getTemperature()

  getHumidity()

  getPressure()

  measurementChanged()

}

rel="File-List" href="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_filelist.xml" />

1.WeatherData具有getter方法,可以取得三个测量值:温度、湿度和气压。我们不在乎这些值“如何”被设置,WeatherData对象自己知道如何从气象站取得更新数据。

2.当新数据备妥时,measurementChanged()方法会被调用(我们不在乎次方法是如何调用,只在乎它被调用了)。

3.我们需要实现三个使用天气数据的布告板:目前状况、气象统计、天气预报。

4.此系统可以扩展,让其他开发人员建立定制的布告板,用户可以随心所欲地添加或删除任何布告板。

 

二、错误的示范

measurementChanged()方法中添加我们的代码:

public class WeatherData

{

public void measurementChanged()

{

float temp = getTemperature();

float humidity = getHumidity();

float pressure = getPressure();

//调用每个布告板更新显示,出入最新的测量。

//但是,针对具体实现编程,会导致我们以后增加或删除布告板时必须修改程序。

//这是改变的地方,需要封装起来。

//不过,至少这里看起来像是一个统一的接口。

//布告板的方法名称都是update(),参数都相同。

currentConditionsDisplay.update(temp,humidity,pressure);

statisticsDisplay.update(temp,humidity,pressure);

forecastDisney.update(temp,humidity,pressure);

}

}

 

三、认识观察着模式

报纸杂志的订阅

1.报社的业务就是出版报纸(主题subject)

2.向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸(注册为观察者observer之后,主题就会不断地向观察者发送最新信息)

3.当你不想再看报纸时,取消订阅,他们就不会再送新报纸(观察者取消注册,不再是观察者,主题不再发送信息)

4.只要报社还在运行,就会一直有人向他们订阅报纸或取消订阅。(可以随时注册成观察者,或者取消注册).

出版者+订阅者=观察者模式

 

四、定义观察者模式及类图

观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,他的所有依赖者都会受到通知并自动更新。

rel="File-List" href="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_filelist.xml" />

 

rel="File-List" href="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_filelist.xml" />

五、松耦合的威力

观察者模式提供了一种对象设计,让主题和观察者之间松耦合。为什么呢?

关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节。

任何时候我们都可以增加新的观察者。因为注意唯一依赖的是一个实现Observer接口的对象列表,事实上,在运行时我们可以用新的观察者取代现有的观察者,主题不会受到任何影响。同样,也可以随时删除某些观察者。

有新类型的观察者出现时,主题的代码不需要修改。假如我们有个新的具体类需要当观察者,我不不需要为了兼容新类型而修改主题的代码,所要做的就是在新的类里实现此观察者接口,然后注册为观察者即可。主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象。

我们可以独立地复用主题或观察者,因为二者并非紧耦合。

改变主题或观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以自由第改变它们。

设计原则:为了交互对象直接的松耦合设计而努力。

松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的相互依赖降到了最低。

 

六、类图

1.主题类

 

 

rel="File-List" href="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_filelist.xml" />

2.观察者类

rel="File-List" href="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_filelist.xml" />

3. 气象站类图

七、源码

观察者模式推push的方式

由主题主动将数据送给观察者。

pull的方式

主题提供一些公开的getter方法,让观察者“拉”走需要的数据。

辩论:

1.主题可以让观察者拉走数据,但是观察者可能需要调用很多次才能收集齐全所要的数据,而推的方式是主题把自己的数据全部传送给观察者,观察者可以在一次通知中得到所有状态数据。

2.但是,观察者种类众多,主题不可能事先料到所要观察者的不同需求。观察者直接去拉走需要的数据,这样一来,如果观察者需要一点点数据,就不会强迫受到一堆数据了。同时,也便于以后扩展。如果主题决定增加更多的状态数据,就不用修改和更新对每个观察者的调用,只需改变自己允许更多的getter方法来取得新增的装态。

1.push的方式

ISubject.cs

interface ISubject

    {

        //这两个方法都需要观察着做参数,该观察者是用来被注册或删除的

        void registerObserver(IObserver o);

        void removeObserver(IObserver o);

        //当主题状态改变时,这个方法被调用,通知所有观察者

        void notifyObserver();

    }



WeatherData.cs

class WeatherData:ISubject

    {

        //加上一个ArrayList来记录观察者,此ArrayList在构造器中建立。

        private ArrayList observers;        

        private float temperature;

        private float humidity;

        private float pressure;

        public WeatherData()

        {

            observers = new ArrayList();

        }

        #region ISubject Members

        //当注册观察者时,我们只要把它加到ArrayList的后面即可

        public void registerObserver(IObserver o)

        {

            observers.Add(o);

        }

        //观察者取消注册,我们把它从ArrayList中删除即可

        public void removeObserver(IObserver o)

        {

            int i = observers.IndexOf(o);

            if(i>=0)

            {

                observers.Remove(o);

            }            

        }

        //此处,我们把状态告诉每一个观察者。

        //因为观察者都实现了update(),所有我们知道如何通知他们.

        public void notifyObserver()

        {

            foreach(IObserver observer in observers)

            {

                observer.update(temperature, humidity,pressure);

            }

        }

        #endregion

        //从气象站得到更新观测值时,我们通知观察者。

        public void measurementsChanged()

        {

            notifyObserver();

        }

        public void setMeasuremensts(float temperature, float humidity, float pressure)

        {

            this.temperature = temperature;

            this.humidity = humidity;

            this.pressure = pressure;

            measurementsChanged();

        }

    }


IObserver.cs

//所有观察者都必须实现update方法,以实现观察者接口。

    interface IObserver

    {

        //当气象观测值改变时,主题会把这些状态当作方法的参数,传送给观察者

        void update(float temp, float humidity, float pressure);

    }


IDisplayElement.cs

//DisplayElement接口只包含了一个display方法,

    //当布告板需要显示时,调用此方法

    interface IDisplayElement

    {

        void display();

    }


StatisticsDisplay.cs

class StatisticsDisplay:IObserver,IDisplayElement

    {

        #region Members

        private float maxTemp = 0.0f;

        private float minTemp = 200;//set intial high so that minimum
 

        //is set first invokation

        private float temperatureSum = 0.0f;

        private int numReadings = 0;

        private ISubject weatherData;

        #endregion//Members


        #region NumberOfReadings Property

        public int NumberOfReadings

        {

            get

            {

                return numReadings;

            }

        }

        #endregion//NumberOfReadings Property


        #region Constructor

        public StatisticsDisplay(ISubject weatherData)

        {

            this.weatherData = weatherData;

            weatherData.registerObserver(this);

        }

        #endregion//Constructor


        #region IObserver Members

        public void update(float temperature, float humidity, float pressure)

        {

            temperatureSum += temperature;

            numReadings++;


            if (temperature > maxTemp)

            {

                maxTemp = temperature;

            }


            if (temperature < minTemp)

            {

                minTemp = temperature;

            }


            display();

        }

        #endregion


        #region IDisplayElement Members

        public void display()

        {

            float average = temperatureSum / numReadings;

            System.Console.WriteLine("Statistics:Avg/Max/Min temperature = {0}F,{1}F,{2}F", 
average, maxTemp, minTemp);          

        }

        #endregion

    }



CurrentConditionsDisplay.cs

class CurrentConditionsDisplay:IObserver, IDisplayElement

    {

        private float temperature;

        private float humidity;  

        private ISubject weatherData;


        public CurrentConditionsDisplay(ISubject weatherData)

        {

            this.weatherData = weatherData;

            weatherData.registerObserver(this);

        }

        #region IObserver Members

        public void update(float temperature, float humidity, float pressure)

        {

            this.temperature = temperature;

            this.humidity = humidity;

            //this.pressure = pressure;

            display();

        }

        #endregion

        #region IDisplayElement Members

        public void display()

        {

            System.Console.WriteLine( "Current conditions: {0}F degrees and {1}% humidity" ,
temperature,humidity );

        }

        #endregion

    }


ForcastDisplay.cs

class ForcastDisplay : IObserver, IDisplayElement

    {

        private float currentPressure = 29.92f;  

		private float lastPressure;

		private ISubject weatherData;

		public ForcastDisplay(ISubject weatherData)

		{

			this.weatherData = weatherData;

			weatherData.registerObserver(this);

		}

		#region IObserver Members

		public void update(float temperature, float humidity, float pressure)

		{

			lastPressure = currentPressure;

			currentPressure = pressure;

                        display();

		}

		#endregion

		#region IDisplayElement Members

		public void display()

		{		

			string str="Forcast:";

			if(currentPressure > lastPressure) 

			{

				str += "Improving weather on the way!";

			}

			else if (currentPressure == lastPressure)

			{

				str += "More of the same";

			} 

			else if (currentPressure < lastPressure)

			{

				str += "Watch out for cooler, rainy weather";

			}
			

            System.Console.WriteLine(str);

		}

		#endregion

	}



Program.cs

class Program

    {

        static void Main(string[] args)

        {

            WeatherData weatherData = new WeatherData();

            CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);

            ForcastDisplay forcastDisplay = new ForcastDisplay(weatherData);

            StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);

            weatherData.setMeasuremensts(80, 65, 30.4F);

        }

    }
    
    

 

rel="File-List" href="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_filelist.xml" />

2.pull的方式

以WeatherData.cs和CurrentConditionsDisplay.cs为例进行说明

WeatherData.cs

class WeatherData:ISubject

    {

        //加上一个ArrayList来记录观察者,此ArrayList在构造器中建立。

        private ArrayList observers;        

        private float temperature;

        private float humidity;

        private float pressure;

        public WeatherData()

        {

            observers = new ArrayList();

        }

        #region ISubject Members

        //当注册观察者时,我们只要把它加到ArrayList的后面即可

        public void registerObserver(IObserver o)

        {

            observers.Add(o);

        }

        //观察者取消注册,我们把它从ArrayList中删除即可

        public void removeObserver(IObserver o)

        {

            int i = observers.IndexOf(o);

            if(i>=0)

            {

                observers.Remove(o);

            }            

        }


        //此处,我们把状态告诉每一个观察者。

        //因为观察者都实现了update(),所有我们知道如何通知他们.

        public void notifyObserver()

        {

            foreach(IObserver observer in observers)

            {

                observer.update();

            }

        }

        #endregion


        //从气象站得到更新观测值时,我们通知观察者。

        public void measurementsChanged()

        {

            notifyObserver();

        }

        public void setMeasuremensts(float temperature, float humidity, float pressure)

        {

            this.temperature = temperature;

            this.humidity = humidity;

            this.pressure = pressure;

            measurementsChanged();

        }


        public float getTemperature()

        {

            return temperature;

        }

        public float getHumidity()

        {

            return humidity;

        }

        public float getPressure()

        {

            return pressure;

        }

    }



CurrentConditionsDisplay.cs

class CurrentConditionsDisplay:IObserver, IDisplayElement

    {

        private float temperature;

        private float humidity;  

        private ISubject weatherData;

        public CurrentConditionsDisplay(ISubject weatherData)

        {

            this.weatherData = weatherData;

            weatherData.registerObserver(this);

        }

        #region IObserver Members

        public void update()

        {

            this.temperature = ((WeatherData)weatherData).getTemperature();

            this.humidity = ((WeatherData)weatherData).getHumidity();

            display();

        }

        #endregion


        #region IDisplayElement Members

        public void display()

        {

            System.Console.WriteLine( "Current conditions: {0}F degrees and {1}% humidity" ,
temperature,humidity );

        }

        #endregion

    }

 

 

八、设计箱内的工具

OO原则:

封装变化

多用组合,少用继承

针对接口编程,不针对实现编程

为交互对象之间的松耦合设计而努力

观察者模式:

在对象之间定义一对多的依赖,这样一来当一个对象改变状态,依赖它的对象都会收到通知,并自动更新。

要点:

观察者模式定义了对象之间一对多的关系。

主题用一个共同的接口来更新观察者。

观察者和主题之间用松耦合方式结合,主题不知道观察者的细节,只知道观察者实现了观察者接口。

使用此模式时,可从主题处推 push 或拉 pull 数据(推的方式被认为更“正确”)

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
观察者模式Observer Pattern)是一种行为型设计模式,它允许一个对象(称为主题或可观察者)在状态变化时通知其他多个对象(称为观察者)。这个模式主要用于解耦主题和观察者,使它们能够独立地进行修改和扩展。 在观察者模式中,主题维护一个观察者列表,可以动态地添加或移除观察者。当主题的状态发生变化时,它会遍历观察者列表,并调用每个观察者的更新方法,将状态变化的信息传递给观察者。观察者可以根据接收到的信息做出相应的操作。 以下是一个简单的示例代码,演示了观察者模式的实现: ```c #include <iostream> #include <vector> // 观察者接口 class Observer { public: virtual void update(int data) = 0; }; // 具体观察者 A class ConcreteObserverA : public Observer { public: void update(int data) override { std::cout << "ConcreteObserverA received: " << data << std::endl; } }; // 具体观察者 B class ConcreteObserverB : public Observer { public: void update(int data) override { std::cout << "ConcreteObserverB received: " << data << std::endl; } }; // 主题 class Subject { private: int data; std::vector<Observer*> observers; public: void attach(Observer* observer) { observers.push_back(observer); } void detach(Observer* observer) { // 从观察者列表中删除观察者 // ... } void notify() { for (Observer* observer : observers) { observer->update(data); } } void setData(int value) { data = value; notify(); } }; int main() { Subject subject; ConcreteObserverA observerA; ConcreteObserverB observerB; subject.attach(&observerA); subject.attach(&observerB); subject.setData(42); return 0; } ``` 在上述示例中,`Subject` 是主题类,维护了一个观察者列表。`Observer` 是观察者接口,定义了一个 `update` 方法用于接收主题的通知。`ConcreteObserverA` 和 `ConcreteObserverB` 是具体的观察者类,实现了 `update` 方法。 在 `main` 函数中,我们创建了一个主题对象 `subject` 和两个观察者对象 `observerA` 和 `observerB`。通过调用 `attach` 方法,将观察者对象添加到主题的观察者列表中。然后,通过调用 `setData` 方法改变主题的状态,并自动通知所有观察者。 当 `subject.setData(42)` 被调用时,观察者 `observerA` 和 `observerB` 的 `update` 方法会被依次调用,输出相应的信息。 这就是观察者模式的基本实现。通过使用观察者模式,主题和观察者之间的耦合性降低,可以方便地扩展和修改代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值