3 ways to implement the observer pattern in C#
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); }
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(); } }
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
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);
Definition of the OnAlarmEvent method
public virtual void OnAlarm(AlarmEventArgs e) { if(Alarm!=null) Alarm(this,e); }
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)); }
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
IObservable
In our scenario, the smart alarm clock is IObservable. This interface only defines one method IDisposable Subscribe(IObserver
public IDisposable Subscribe(IObserver<AlarmData> observer) { if (!_observers.Contains(observer)) { _observers.Add(observer); } return new DisposedAction(() => _observers.Remove(observer)); }
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)); }
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); }
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));
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); }
方法签名中要接受一个Action
public override void ItIsTimeToAlarm() { if (_alarmAction != null) { var alarmData = new AlarmData(_alarmTime.Value, 0.92m); _alarmAction(alarmData); } }
牛奶加热器中使用这种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); });
在实际使用过程中我会把这种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; }
牛奶加热器中进行调用:
_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); });
显然改进后的写法语义更好:闹钟.设置闹铃时间().当报警时(()=>{执行以下功能})
这种函数式写法更简练,但是也有明显的缺点,该模型不支持多个观察者,当面包烘烤机使用这样的API时,会覆盖牛奶加热器的函数,即每次只支持一个观察者使用。
结束语,本文总结了.net下的三种观察者模型实现方案,能在编程场景下选择最合适的模型当然是我们的最终目标。

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

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

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

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.

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.

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.

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 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.

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.

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

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
