Home > Backend Development > C#.Net Tutorial > 3 ways to implement the observer pattern in C#

3 ways to implement the observer pattern in C#

黄舟
Release: 2016-12-12 15:37:23
Original
1399 people have browsed it

Speaking of observer mode, you can probably find a lot of them in the garden. So the purpose of writing this blog is twofold:

1. The observer pattern is a necessary pattern for writing loosely coupled code. Its importance is self-evident. Regardless of the code level, many components adopt the Publish-Subscribe pattern. So I want to redesign a usage scenario according to my own understanding and use the observer pattern flexibly in it
2. I want to make a summary of the three solutions to implement the observer pattern in C#. I haven’t seen such a summary yet

Now let’s assume such a scenario and use the observer mode to realize the requirements:

In the future, smart homes will enter every household, and each home will have APIs for customers to customize and integrate, so the first smart alarm clock ( smartClock) comes on the scene first. The manufacturer provides a set of APIs for this alarm clock. When an alarm time is set, the alarm clock will notify you at this time. Our smart milk heater, bread baking machine, and toothpaste squeezing equipment all need to be subscribed. This alarm clock alarm message automatically prepares milk, bread, toothpaste, etc. for the owner.

This scenario is a very typical observer mode. The alarm clock of the smart alarm clock is a subject, and the milk warmer, bread baking machine, and toothpaste squeezing equipment are observers. They only need to subscribe to this topic. Implement a loosely coupled coding model. Let's implement this requirement through three options one by one.

1. Use the Event model of .net to implement

The Event model in .net is a typical observer pattern. It has been widely used in codes after .net was born. Let’s see how the event model can be used in this For use in scenarios,

First introduce the smart alarm clock. The manufacturer provides a set of very simple API

public void SetAlarmTime(TimeSpan timeSpan)
        {
            _alarmTime = _now().Add(timeSpan);
            RunBackgourndRunner(_now, _alarmTime);
        }
Copy after login

SetAlarmTime(TimeSpan timeSpan) is used for timing. When the user sets a time, the alarm clock will run a loop similar to while(true) in the background to compare the time. When the alarm time is up, a notification event will be sent out

protected void RunBackgourndRunner(Func<DateTime> now,DateTime? alarmTime )
        {
            if (alarmTime.HasValue)
            {
                var cancelToken = new CancellationTokenSource();
                var task = new Task(() =>
                {
                    while (!cancelToken.IsCancellationRequested)
                    {
                        if (now.AreEquals(alarmTime.Value))
                        {
                            //闹铃时间到了
                            ItIsTimeToAlarm();
                            cancelToken.Cancel();
                        }
                        cancelToken.Token.WaitHandle.WaitOne(TimeSpan.FromSeconds(2));
                    }
                }, cancelToken.Token, TaskCreationOptions.LongRunning);
                task.Start();
            }
        }
Copy after login

Other codes are not Important, the key point is to execute ItIsTimeToAlarm() when the alarm time is up; We send events here to notify subscribers. There are three elements to implement the event model in .net,

1. Define an event for the subject, public event Action Alarm;

2. Define an EventArgs for the subject’s information, namely AlarmEventArgs, which contains all the information of the event

3. The subject emits events in the following ways

var args = new AlarmEventArgs(_alarmTime.Value, 0.92m);
 OnAlarmEvent(args);
Copy after login

Definition of the OnAlarmEvent method

public virtual void OnAlarm(AlarmEventArgs e)
       {
           if(Alarm!=null)
               Alarm(this,e);
       }
Copy after login

Pay attention to naming here, event content-AlarmEventArgs, event-Alarm (verb, such as KeyPress), method to trigger the event void OnAlarm(), these elements must comply with the naming convention of the event model.
The smart alarm clock (SmartClock) has been implemented. We subscribe to this Alarm message in the milk heater (MilkSchedule):

public void PrepareMilkInTheMorning()
        {
            _clock.Alarm += (clock, args) =>
            {
                Message =
                    "Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%".FormatWith(
                        args.AlarmTime, args.ElectricQuantity*100);
 
                Console.WriteLine(Message);
            };
 
            _clock.SetAlarmTime(TimeSpan.FromSeconds(2));
 
        }
Copy after login

It can also be used in the bread baking machine _clock.Alarm+=(clock,args)=> {//it is time to roast bread}subscribe to alarm messages.

At this point, the event model has been introduced. The implementation process is still a bit cumbersome, and improper use of the event model will cause memory The problem of leak is that when the observer subscribes to a topic with a long life cycle (the topic life cycle is longer than the observer), the observer will not be memory recycled (because there are still references to the topic), see Understanding for details and Avoiding Memory Leaks with Event Handlers and Event Aggregators, developers need to explicitly unsubscribe from the topic (-=).

Old A in the garden also wrote a blog on how to use weak references to solve this problem: How to solve the Memory Leak problem caused by events: Weak Event Handlers.

2. Use IObservable and IObserverImplementing the Observer Pattern

IObservable As the name implies - observable things, that is, subjects, Observer is obviously an observer.

In our scenario, the smart alarm clock is IObservable. This interface only defines one method IDisposable Subscribe(IObserver observer); The name of this method is a bit confusing. Subscribe means subscription, which is different from the previously mentioned observer (observer) subscribing to the subject (subject). Here, the subject subscribes to the observer. In fact, this makes sense, because under this model, the subject maintains a list of observers, so there is a saying that the subject subscribes to the observer. , let’s look at the alarm clock’s IDisposable Subscribe (IObserver observer) implementation:

public IDisposable Subscribe(IObserver<AlarmData> observer)
        {
            if (!_observers.Contains(observer))
            {
                _observers.Add(observer);
            }
            return new DisposedAction(() => _observers.Remove(observer));
        }
Copy after login


You can see that an observer list_observers is maintained here. After the alarm clock reaches the time, it will traverse all the observer lists and notify the observers one by one of the messages

public override void ItIsTimeToAlarm()
        {
            var alarm = new AlarmData(_alarmTime.Value, 0.92m);
            _observers.ForEach(o=>o.OnNext(alarm));
        }
Copy after login

Obviously, the observer has an OnNext method. The method signature is an AlarmData, which represents the message data to be notified. Next, let’s look at the implementation of the milk heater. As an observer, the milk heater must of course implement IObserver Interface

public  void Subscribe(TimeSpan timeSpan)
       {
           _unSubscriber = _clock.Subscribe(this);
           _clock.SetAlarmTime(timeSpan);
       }
 
       public  void Unsubscribe()
       {
           _unSubscriber.Dispose();
       }
 
       public void OnNext(AlarmData value)
       {
                      Message =
                  "Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%".FormatWith(
                      value.AlarmTime, value.ElectricQuantity * 100);
           Console.WriteLine(Message);
       }
Copy after login

In addition, in order to facilitate the use of the bread baker, we have also added two methods, Subscribe() and Unsubscribe(), see the calling process

var milkSchedule = new MilkSchedule();
//Act
milkSchedule.Subscribe(TimeSpan.FromSeconds(12));
Copy after login

3. Action functional solution

Before introducing the solution I need to explain that this solution is not an observer model, but it can achieve the same function and is simpler to use, which is also one of my favorite uses.

这种方案中,智能闹钟(smartClock)提供的API需要设计成这样:

public void SetAlarmTime(TimeSpan timeSpan,Action<AlarmData> alarmAction)
       {
           _alarmTime = _now().Add(timeSpan);
           _alarmAction = alarmAction;
           RunBackgourndRunner(_now, _alarmTime);
       }
Copy after login

方法签名中要接受一个Action,闹钟在到点后直接执行该Action即可:

public override void ItIsTimeToAlarm()
       {
           if (_alarmAction != null)
           {
               var alarmData = new AlarmData(_alarmTime.Value, 0.92m);
               _alarmAction(alarmData);    
           }
       }
Copy after login

牛奶加热器中使用这种API也很简单:

_clock.SetAlarmTime(TimeSpan.FromSeconds(1), (data) =>
            {
                Message =
                   "Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%".FormatWith(
                       data.AlarmTime, data.ElectricQuantity * 100);
            });
Copy after login

在实际使用过程中我会把这种API设计成fluent模型,调用起来代码更清晰:

智能闹钟(smartClock)中的API:

public Clock SetAlarmTime(TimeSpan timeSpan)
        {
            _alarmTime = _now().Add(timeSpan);
            RunBackgourndRunner(_now, _alarmTime);
            return this;
        }
 
        public void OnAlarm(Action<AlarmData> alarmAction)
        {
            _alarmAction = alarmAction;
        }
Copy after login

牛奶加热器中进行调用:

_clock.SetAlarmTime(TimeSpan.FromSeconds(2))
      .OnAlarm((data) =>
                {
                    Message =
                    "Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%".FormatWith(
                        data.AlarmTime, data.ElectricQuantity * 100);
                });
Copy after login

显然改进后的写法语义更好:闹钟.设置闹铃时间().当报警时(()=>{执行以下功能})

这种函数式写法更简练,但是也有明显的缺点,该模型不支持多个观察者,当面包烘烤机使用这样的API时,会覆盖牛奶加热器的函数,即每次只支持一个观察者使用。

结束语,本文总结了.net下的三种观察者模型实现方案,能在编程场景下选择最合适的模型当然是我们的最终目标。


Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template