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

3 ways to implement the observer pattern in C#

Dec 12, 2016 pm 03:37 PM

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下的三种观察者模型实现方案,能在编程场景下选择最合适的模型当然是我们的最终目标。


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

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

How to use various symbols in C language How to use various symbols in C language Apr 03, 2025 pm 04:48 PM

The usage methods of symbols in C language cover arithmetic, assignment, conditions, logic, bit operators, etc. Arithmetic operators are used for basic mathematical operations, assignment operators are used for assignment and addition, subtraction, multiplication and division assignment, condition operators are used for different operations according to conditions, logical operators are used for logical operations, bit operators are used for bit-level operations, and special constants are used to represent null pointers, end-of-file markers, and non-numeric values.

What is the role of char in C strings What is the role of char in C strings Apr 03, 2025 pm 03:15 PM

In C, the char type is used in strings: 1. Store a single character; 2. Use an array to represent a string and end with a null terminator; 3. Operate through a string operation function; 4. Read or output a string from the keyboard.

How to handle special characters in C language How to handle special characters in C language Apr 03, 2025 pm 03:18 PM

In C language, special characters are processed through escape sequences, such as: \n represents line breaks. \t means tab character. Use escape sequences or character constants to represent special characters, such as char c = '\n'. Note that the backslash needs to be escaped twice. Different platforms and compilers may have different escape sequences, please consult the documentation.

How to convert char in C language How to convert char in C language Apr 03, 2025 pm 03:21 PM

In C language, char type conversion can be directly converted to another type by: casting: using casting characters. Automatic type conversion: When one type of data can accommodate another type of value, the compiler automatically converts it.

The difference between multithreading and asynchronous c# The difference between multithreading and asynchronous c# Apr 03, 2025 pm 02:57 PM

The difference between multithreading and asynchronous is that multithreading executes multiple threads at the same time, while asynchronously performs operations without blocking the current thread. Multithreading is used for compute-intensive tasks, while asynchronously is used for user interaction. The advantage of multi-threading is to improve computing performance, while the advantage of asynchronous is to not block UI threads. Choosing multithreading or asynchronous depends on the nature of the task: Computation-intensive tasks use multithreading, tasks that interact with external resources and need to keep UI responsiveness use asynchronous.

The difference between char and wchar_t in C language The difference between char and wchar_t in C language Apr 03, 2025 pm 03:09 PM

In C language, the main difference between char and wchar_t is character encoding: char uses ASCII or extends ASCII, wchar_t uses Unicode; char takes up 1-2 bytes, wchar_t takes up 2-4 bytes; char is suitable for English text, wchar_t is suitable for multilingual text; char is widely supported, wchar_t depends on whether the compiler and operating system support Unicode; char is limited in character range, wchar_t has a larger character range, and special functions are used for arithmetic operations.

What is the difference between char and unsigned char What is the difference between char and unsigned char Apr 03, 2025 pm 03:36 PM

char and unsigned char are two data types that store character data. The main difference is the way to deal with negative and positive numbers: value range: char signed (-128 to 127), and unsigned char unsigned (0 to 255). Negative number processing: char can store negative numbers, unsigned char cannot. Bit mode: char The highest bit represents the symbol, unsigned char Unsigned bit. Arithmetic operations: char and unsigned char are signed and unsigned types, and their arithmetic operations are different. Compatibility: char and unsigned char

Common errors and ways to avoid char in C language Common errors and ways to avoid char in C language Apr 03, 2025 pm 03:06 PM

Errors and avoidance methods for using char in C language: Uninitialized char variables: Initialize using constants or string literals. Out of character range: Compare whether the variable value is within the valid range (-128 to 127). Character comparison is case-insensitive: Use toupper() or tolower() to convert character case. '\0' is not added when referencing a character array with char*: use strlen() or manually add '\0' to mark the end of the array. Ignore the array size when using char arrays: explicitly specify the array size or use sizeof() to determine the length. No null pointer is not checked when using char pointer: Check whether the pointer is NULL before use. Use char pointer to point to non-character data

See all articles