在.NET中实现观察者模式(3种技术)

目录

介绍

背景

示例场景

实现

观察者模式(在.NET 4.0之前版本)

<技术#1>

<技术#2>

<技术#3>


.NET中实现观察者模式的多种技术

介绍

我们通过多年的软件开发职业生涯了解了设计模式。在所有模式中,我遇到的最酷和最实用的是观察者模式又叫发布-订阅模式。您将找到有关如何使用框架中提供的程序集在.NET Framework中实现观察者模式的各种文章。但是,.NET Framework已经发展了多年,与此同时,它还提供了用于创建观察者模式的新库。

例如,在.NET 4.0之前,我们必须编写自定义代码来实现观察者 Pattern或使用委托和事件。随着框架4.0的发布,出现了非常酷的用于实现观察者模式的IObservableI观察者接口。我们将通过本文中的代码示例介绍所有这些技术。

背景

为了简要介绍观察者模式,它定义了对象(发布者和多个订阅者)之间的一对多依赖关系,这样当一个对象(Publisher)更改状态时,所有依赖对象(Subscriber)都会自动得到通知和更新。

上述定义受Head First Design Patterns的影响很大。我强烈建议任何开始学习设计模式的人阅读本书。

示例场景

下面的代码基于一个场景,其中我们有一个记录天气数据(温度、湿度和压力)的气象站。

该数据必须由多个显示器使用并相应地显示。每当气象站有可用的新数据时,它应该将数据推送到应该相应更新的显示器。假设我们有3个显示器(当前条件,统计显示和预测显示),只要气象站有新数据,所有这些都必须更新。

该场景类似于“ Head First Design Patterns ” 书中提供的场景,但在这里,我们将讨论相同的.NET实现。

实现

因此,Weather Data对象实际上是一个具有3个属性的对象——温度,湿度和压力。

WeatherData类如下所示:

public class WeatherData
    {
        public float Temperature { get; set; }
        public float Humidity { get; set; }
        public float Pressure { get; set; }

        public WeatherData(float temp, float humid, float pres)
        {
            Temperature = temp;
            Humidity = humid;
            Pressure = pres;
        }
    }
}

现在,只要气象站有新数据,就需要将这条数据推送到天气显示器(订阅者)。

我们的代码示例将重点介绍在.NET 4.0之前版本和.NET 4.0之后版本实现观察者模式。

每个部分中的代码将分为发布者代码(类和接口)和订户代码(类和接口)。让我们继续吧。

观察者模式(在.NET 4.0之前版本)

<技术#1>

使用纯面向对象(OO)编程概念

根据面向对象(OO)最佳实践,我们应该总是尝试编写接口而不是实现。以下是基于此的内容。

发布者(Publisher)

在这里的Publisher实际上是一个气象数据提供者(Weather Data Provider)提供的气象数据。这个WeatherDataProvider类实现了IPublisher接口如下图所示:

public interface IPublisher
    {
        void RegisterSubscriber(ISubscriber subscriber);
        void RemoveSubscriber(ISubscriber subscriber);
        void NotifySubscribers();
    }

上述三种方法执行以下操作:

  • RegisterSubscriber——使用Publisher注册一个新的订阅者。发布者必须将订阅者添加到它必须通知的订阅者列表中,无论何时WeatherData发生变化。
  • RemoveSubscriber——从publisher通知列表中删除已注册的subscriber
  • NotifySubscibers——此方法实际上调用subscriber对象上的方法以通知某些新WeatherData的可用

WeatherDataProvider的具体实现如下:

public class WeatherDataProvider : IPublisher
    {
        List<ISubscriber> ListOfSubscribers;
        WeatherData data;
        public WeatherDataProvider()
        {
            ListOfSubscribers = new List<ISubscriber>();
        }
        public void RegisterSubscriber(ISubscriber subscriber)
        {
            ListOfSubscribers.Add(subscriber);
        }

        public void RemoveSubscriber(ISubscriber subscriber)
        {
            ListOfSubscribers.Remove(subscriber);
        }

        public void NotifySubscribers()
        {
            foreach (var sub in ListOfSubscribers)
            {
                sub.Update(data);
            }
        }

        private void MeasurementsChanged()
        {
            NotifySubscribers();
        }
        public void SetMeasurements(float temp, float humid, float pres)
        {
            data = new WeatherData(temp, humid, pres);           
            MeasurementsChanged();
        }
    }

订阅者(Subscriber)

这里的订阅者实际上是使用数据的天气显示。每个订阅者都应该实现ISubscriber interface

public interface ISubscriber
    {
        void Update(WeatherData data);
    }

Subscriber interface只是有一个方法它显示从WeatherDataProvider中收到的当前WeatherData

CurrentConditionsDisplay的实现如下:

public class CurrentConditionsDisplay : ISubscriber
    {
        WeatherData data;
        IPublisher weatherData;

        public CurrentConditionsDisplay(IPublisher weatherDataProvider)
        {
            weatherData = weatherDataProvider;
            weatherData.RegisterSubscriber(this);
        }

        public void Display()
        {
            Console.WriteLine("Current Conditions : 
            Temp = {0}Deg | Humidity = {1}% | 
            Pressure = {2}bar", data.Temperature, data.Humidity, data.Pressure);
        }
       
        public void Update(WeatherData data)
        {
            this.data = data;
            Display();
        }
    }

在上面的代码中,我们完成了依赖注入或IoC,其中我们通过构造函数注入了IPublisher interface构造函数注入是面向对象(OO)编程中非常常见的做法。

因此,在上面的代码中发生的情况是,当实例化显示时,它调用WeatherDataProvider中的RegisterSubscriber方法并将其自身注册为感兴趣的订阅者。

如果显示器要取消注册,则必须调用WeatherDataProvider中的RemoveSubscriber方法。有多种方法可以实现它——您可以调用Destructor(如上所述)中的RemoveSubscriber,也可以实现IDisposable并调用Dispose 方法中的RemoveSubscriber,或者只需生成一个类方法并在那里进行调用。

CurrentConditionsDisplay,我们也可能有一个ForecastDisplay(如下所示):

public class ForecastDisplay : ISubscriber, IDisposable
    {
        WeatherData data;
        IPublisher weatherData;

        public ForecastDisplay(IPublisher weatherDataProvider)
        {
            weatherData = weatherDataProvider;
            weatherData.RegisterSubscriber(this);
        }

        public void Display()
        {
            Console.WriteLine("Forecast Conditions : Temp = {0}Deg | 
                Humidity = {1}% | Pressure = {2}bar", data.Temperature + 6, 
                data.Humidity + 20, data.Pressure - 3);
        }

        public void Update(WeatherData data)
        {
            this.data = data;
            Display();
        }

        public void Dispose()
        {
            weatherData.RemoveSubscriber(this);
        }
    }

同样,您可以在此处定义任意数量的显示,并且可以为更新订阅WeatherStation

为了演示上面的代码,我们将制作一个示例控制台应用程序,如下所示:

class Program
    {
        static void Main(string[] args)
        {
            WeatherDataProvider weatherData = new WeatherDataProvider();

            CurrentConditionsDisplay currentDisp = new CurrentConditionsDisplay(weatherData);            
            ForecastDisplay forecastDisp = new ForecastDisplay(weatherData);

            weatherData.SetMeasurements(40, 78, 3);
            Console.WriteLine();
            weatherData.SetMeasurements(45, 79, 4);
            Console.WriteLine();
            weatherData.SetMeasurements(46, 73, 6);

            //Remove forecast display
            forecastDisp.Dispose();
            Console.WriteLine();
            Console.WriteLine("Forecast Display removed");
            Console.WriteLine();
            weatherData.SetMeasurements(36, 53, 8);

            Console.Read();
        }
    }

输出应如下:

https://i-blog.csdnimg.cn/blog_migrate/7709011b19cd68365a745bc4efddcf5a.png

<技术#2>

使用事件和委托

现在,进入在.NET中实现观察者模式的第二种方式。当然,第一种方法没有使用任何.NET库来实现模式。

无论何时实现观察者模式,这种技术在.NET中都很常见。它利用.NET框架中的事件和委托来实现相同的目的。

我不会深入研究下面的源代码的细节,因为大部分内容都是自解释的,但如果您确实需要有关以下实现的更多信息,可以参考MSDN链接

首先,这种技术利用泛型EventHandler<T>委托来实现模式。基本理论非常简单:

  • 发布者有一个public EventEventHandler,其当WeatherData更改时都会引发。
  • 订阅者附加到EventHandler,当事件发生时都会收到通知。
  • EventArgs e,在订阅者处接收的数据包含有关当前天气状况的数据。

首先,我们必须创建一个继承自EventArgs的类WeatherEventArgs

public class WeatherDataEventArgs : EventArgs
   {
       public WeatherData data { get; private set; }
       public WeatherDataEventArgs(WeatherData data)
       {
           this.data = data;
       }
   }

此类包含将在事件(Event)处理程序中作为事件(Event)参数传递的Weather Data

发布者(Publisher)

这里的Publisher是一个叫WeatherDataProvider的类,它会在天气数据发生变化时引发事件并通知订阅的显示:

   

public class WeatherDataProvider : IDisposable
    {
        public event EventHandler<WeatherDataEventArgs> RaiseWeatherDataChangedEvent;

        protected virtual void OnRaiseWeatherDataChangedEvent(WeatherDataEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<WeatherDataEventArgs> handler = RaiseWeatherDataChangedEvent;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        public void NotifyDisplays(float temp, float humid, float pres)
        {
            OnRaiseWeatherDataChangedEvent
                 (new WeatherDataEventArgs(new WeatherData(temp, humid, pres)));
        }

        public void Dispose()
        {
            if (RaiseWeatherDataChangedEvent != null)
            {
                foreach (EventHandler<WeatherDataEventArgs> 
                item in RaiseWeatherDataChangedEvent.GetInvocationList())
                {
                    RaiseWeatherDataChangedEvent -= item;
                }
            }
        }
    }

上面的代码声明了EventHandler泛型类型的委托,它接受WeatherDataEventArgs作为参数提供给事件处理程序。

每次NotifyDisplays方法被调用时,都会引发OnRaiseWeatherDataChangedEvent,然后在下面代码中调用相应Displays(订阅者)中的事件处理程序。

订阅者(Subscriber)

CurrentConditionsDisplay的实施如下。我们可以通过Event处理程序在Event上附加任意数量的显示。

public class CurrentConditionsDisplay
   {
       WeatherData data;
       WeatherDataProvider WDprovider;

       public CurrentConditionsDisplay(WeatherDataProvider provider)
       {
           WDprovider = provider;
           WDprovider.RaiseWeatherDataChangedEvent += provider_RaiseWeatherDataChangedEvent;
       }

       void provider_RaiseWeatherDataChangedEvent(object sender, WeatherDataEventArgs e)
       {
           data = e.data;
           UpdateDisplay();
       }

       public void UpdateDisplay()
       {
           Console.WriteLine("Current Conditions : Temp = {0}Deg |
           Humidity = {1}% | Pressure = {2}bar", data.Temperature, data.Humidity, data.Pressure);
       }

       public void Unsubscribe()
       {
           WDprovider.RaiseWeatherDataChangedEvent -= provider_RaiseWeatherDataChangedEvent;
       }
   }

与上面的一样,我们也可以有ForecastDisplay(在源代码中可用)。

可以使用控制台应用程序完成上述用法的演示,代码如下:

class Program
    {
        static void Main(string[] args)
        {
            WeatherDataProvider provider = new WeatherDataProvider();

            CurrentConditionsDisplay current = new CurrentConditionsDisplay(provider);            
            ForecastDisplay forecast = new ForecastDisplay(provider);

            provider.NotifyDisplays(40, 78, 3);
            Console.WriteLine();
            provider.NotifyDisplays(42, 68, 5);
            Console.WriteLine();
            provider.NotifyDisplays(45, 68, 8);
            Console.WriteLine();
            forecast.Unsubscribe();
            Console.WriteLine("Forecast Display removed");
            Console.WriteLine();
            provider.NotifyDisplays(30, 58, 1);

            //Code to call to detach all event handler
            provider.Dispose();

            Console.Read();
        }
    }

观察者模式(.NET 4.0及更高版本)

.NET框架发布时,它附带了大量针对C#语言的请求语言功能,如动态类型,可选参数,IObservable等。其中一些功能可以在其他语言/框架中使用,但不能在C#中使用。

其中最引人注目的是IObservable<T>IObserver<T>。这些在C#之前已经在Java中提供,它确实是我最喜欢的功能之一。这些集合带来了我们开发人员利用的一些独特而又漂亮的功能。最重要的是能够实现强大的发布者-订阅者模型,又名观察者模式。

以下实现与MSDN链接中的示例实现一致。

<技术#3>

使用IObservable<T>IObserver<T>

这些库使得它最容易实现观察者模式。实际上,在使用这些库时,您可能甚至没有意识到您在程序中使用了发布者-订阅者模型。

在泛型类型IObservable<T>IObserver<T>中,T在我们的示例中是WeatherData

发布者(Publisher)

发布者(WeatherDataProvider)必须实现IObservable<T>接口(如下所示)。

public class WeatherDataProvider : IObservable<WeatherData>
    {
        List<IObserver<WeatherData>> observers;

        public WeatherDataProvider()
        {
            observers = new List<IObserver<WeatherData>>();
        }

        public IDisposable Subscribe(IObserver<WeatherData> observer)
        {
            if (!observers.Contains(observer))
            {
                observers.Add(observer);
            }
            return new UnSubscriber(observers, observer);
        }

        private class UnSubscriber : IDisposable
        {
            private List<IObserver<WeatherData>> lstObservers;
            private IObserver<WeatherData> observer;

            public UnSubscriber(List<IObserver<WeatherData>> ObserversCollection, 
                                IObserver<WeatherData> observer)
            {
                this.lstObservers = ObserversCollection;
                this.observer = observer;
            }

            public void Dispose()
            {
                if (this.observer != null)
                {
                    lstObservers.Remove(this.observer);
                }
            }
        }

        private void MeasurementsChanged(float temp, float humid, float pres)
        {
            foreach (var obs in observers)
            {
                obs.OnNext(new WeatherData(temp, humid, pres));
            }
        }

        public void SetMeasurements(float temp, float humid, float pres)
        {
            MeasurementsChanged(temp, humid, pres);
        }
    }

观察者通过调用其IObservable<T>.Subscribe方法来注册接收通知,该方法将observer 对象的引用分配给private泛型List<T>对象。该方法返回一个Unsubscriber对象,它是一个IDisposable实现,其使观察者能够停止接收通知。该Unsubscriber类是实现一个简单的嵌套类,其实现了IDisposable接口,也保留订阅用户的列表,并被观察者(在我们的例子中的Displays)使用以退订。我们将在下一节中了解更多相关信息。

此外,还有一个我们在订阅者/观察者上调用的OnNext函数。此函数实际上是一个内置函数IObserver ,其表明集合中的某些内容已发生变化。这实际上是通知订阅者更改的功能。

OnNext之外,还有OnErrorOnCompleted功能。我们将在下一节讨论所有这些内容。

订阅者(Subscriber)

订阅者(Displays)必须实现IObserver<T> interfaceCurrentConditionsDisplay的实现如下。我们可以有类似的任意数量的显示(参见源代码):

public class CurrentConditionsDisplay : IObserver<WeatherData>
    {
        WeatherData data;
        private IDisposable unsubscriber;

        public CurrentConditionsDisplay()
        {

        }
        public CurrentConditionsDisplay(IObservable<WeatherData> provider)
        {
            unsubscriber = provider.Subscribe(this);
        }
        public void Display()
        {
            Console.WriteLine("Current Conditions : Temp = {0}Deg | 
            Humidity = {1}% | Pressure = {2}bar", data.Temperature, data.Humidity, data.Pressure);
        }

        public void Subscribe(IObservable<WeatherData> provider)
        {
            if (unsubscriber == null)
            {
                unsubscriber = provider.Subscribe(this);
            }
        }

        public void Unsubscribe()
        {
            unsubscriber.Dispose();
        }

        public void OnCompleted()
        {
            Console.WriteLine("Additional temperature data will not be transmitted.");
        }

        public void OnError(Exception error)
        {
            Console.WriteLine("Some error has occurred..");
        }

        public void OnNext(WeatherData value)
        {
            this.data = value;
            Display();
        }
    }

上述代码中感兴趣的内容如下:

  • 当您调用subscriber方法时,它返回一个实现了IDisposable(在本例中是Unsubscriber)的对象。因此,当我们调用该对象的Dispose时,它会自动调用Unsubscribe
  • 发布者/提供者可以在订阅者/观察者上调用3种方法:
    • IObserver<T>.OnNext方法向观察者传递T对象,其具有当前数据,已更改数据或新数据。
    • IObserver<T>.OnError 方法用于通知观察者已发生某些错误情况(请注意,只要在提供程序中发生异常,就不会自动调用它。它实际上是程序员在提供程序中捕获异常然后调用此函数的责任)。
    • IObserver<T>.OnCompleted 方法通知观察者它已完成发送通知。

可以使用具有以下代码的控制台应用程序来制作用于观察此代码的演示程序:

class Program
   {
       static void Main(string[] args)
       {
           WeatherDataProvider weatherDataO = new WeatherDataProvider();

           CurrentConditionsDisplay currentDisp = new CurrentConditionsDisplay(weatherDataO);

           ForecastDisplay forecastDisp = new ForecastDisplay(weatherDataO);

           weatherDataO.SetMeasurements(40, 78, 3);
           Console.WriteLine();
           weatherDataO.SetMeasurements(45, 79, 4);
           Console.WriteLine();
           weatherDataO.SetMeasurements(46, 73, 6);
           //Remove forecast display
           forecastDisp.Unsubscribe();

           Console.WriteLine();
           Console.WriteLine("Forecast Display removed");
           Console.WriteLine();
           weatherDataO.SetMeasurements(36, 53, 8);

           Console.Read();
       }
   }

 

原文地址:https://www.codeproject.com/Articles/796075/Implement-Observer-Pattern-in-NET-3-Techniques

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值