背景
使用winforms做上位机软件,软件功能简单来说就是与串口通信。
因为一个软件要应用于不同型号的下位机,采用MVP架构提高代码复用性。
其中Model层中实例化SerialPort 对象:
private SerialPort _serialPort;
只关注串口收发。
presenter层负责主要业务逻辑。
view层负责界面更新。这次讨论不涉及到,暂且不谈。
从 Model 或 View 层主动向 Presenter 传递数据或调用处理逻辑
当model层收到数据后,根据单一职责原则(SRP),后续处理需要在presenter层中实现。
这就有了标题写的“从 Model 或 View 层主动向 Presenter 传递数据或调用处理逻辑”这个需求。
这时候,我的第一思维可能是在presenter中写个处理方法然后在model中调用。
但是,通常情况下,Presenter 会实例化 Model 和 View,并持有它们的引用。
Model 和 View 并不知道 Presenter 的存在,也不应该直接调用 Presenter 的方法。
因此,从客观上讲,Model 无法直接调用 Presenter 中的处理逻辑。
这样的话就只能传递数据过去,然后提醒那边接收。这就完全符合事件机制(事件驱动编程)的使用场景。
**在 Model 中定义事件:**Model 定义一个或多个事件,用于在数据发生变化或接收到新数据时通知订阅者。
public class MVP_SerialPortModel { // 定义数据接收事件 public event EventHandler<DataEventArgs> DataReceived; // 触发事件的方法 private void OnSerialDataReceived(object sender, SerialDataReceivedEventArgs e) { byte[] data = ReadDataFromSerialPort(); DataReceived?.Invoke(this, new DataEventArgs(data)); } }
**在 Presenter 中订阅事件:**Presenter 实例化 Model,并订阅其事件。当事件被触发时,Presenter 的事件处理方法被调用,可以继续处理业务逻辑。
public class Presenter { private MVP_SerialPortModel model; public Presenter() { model = new MVP_SerialPortModel(); model.DataReceived += OnDataReceived; } private void OnDataReceived(object sender, DataEventArgs e) { byte[] data = e.Data; // 处理数据的业务逻辑 } }
触发顺序如下:
-
数据到达串口:
- 串口接收到数据。
-
SerialPort
触发DataReceived
事件:SerialPort
类触发其内置的DataReceived
事件。- 由于
SerialPortManager
已经订阅了该事件:serialPort.DataReceived += OnSerialDataReceived;
- 因此,
OnSerialDataReceived
方法被调用。
-
OnSerialDataReceived
方法执行:- 在该方法中,从串口读取数据(字节数组)。
- 读取数据后,
SerialPortManager
触发其自定义的DataReceived
事件,将数据传递给订阅者: DataReceived?.Invoke(this, new DataEventArgs(buffer));
-
SerialPortManager
的DataReceived
事件触发:- 由于
SomeClass
已经订阅了SerialPortManager
的DataReceived
事件:portManager.DataReceived += OnDataReceived;
- 因此,
OnDataReceived
方法被调用。
- 由于
-
OnDataReceived
方法执行:- 接收并处理传递过来的数据。
- 如果需要更新 UI,使用
Invoke
确保在主线程上执行。
为什么在 Model 中定义事件,由 Presenter 订阅是正确的做法
-
**解耦合:**通过事件机制,Model 不需要知道谁会订阅事件,也不需要依赖于具体的 Presenter。这样可以保持 Model 的独立性和可重用性。
-
**职责分离:**Model 专注于数据的收发和存储,业务逻辑和处理则由 Presenter 负责。这符合单一职责原则(SRP)。
-
**遵循 MVP 模式:**在 MVP 模式中,Presenter 充当中介者,负责处理业务逻辑和更新 View。Model 和 View 不直接通信,也不应该依赖于 Presenter。
-
**避免循环依赖:**如果 Model 直接调用 Presenter 的方法,会导致循环依赖,破坏层次化设计,增加代码的复杂性和维护成本。
相关的设计模式和概念
-
**观察者模式(Observer Pattern):**这是一种行为型设计模式,定义了对象间的一对多依赖。当一个对象的状态发生变化时,所有依赖于它的对象都会自动收到通知并更新。这正是事件机制背后的原理。
-
**事件驱动编程:**程序的流程是由事件的发生来驱动的。在 .NET 中,事件和委托提供了对事件驱动编程的良好支持。
-
**依赖倒置原则(Dependency Inversion Principle,DIP):**高层模块不应该依赖于低层模块,二者都应该依赖于抽象。通过事件和接口,可以实现依赖倒置,降低模块间的耦合。
事件的参数类
这个事件上文中的事件:
public event EventHandler<DataEventArgs> DataReceived;
中涉及到的参数类也需要定义一下:
//更新后的 DataEventArgs 类,用于封装字节数据 public class DataEventArgs : EventArgs { public byte[] Data { get; } public DataEventArgs(byte[] data) { Data = data; } }
1. 关于 EventArgs
EventArgs
是 .NET 中用于传递事件数据的基类。它是一个空的类,表示事件不包含任何数据。如果你需要在事件中传递数据,可以创建一个从 EventArgs
派生的自定义类,包含你需要的属性。
示例:
- **没有数据的事件:**使用
EventArgs.Empty
,表示事件不包含任何数据。 - **包含数据的事件:**创建一个继承自
EventArgs
的类,添加需要的属性。
2. 事件的定义与 EventHandler<TEventArgs>
在 .NET 中,事件通常使用委托类型 EventHandler
或 EventHandler<TEventArgs>
定义:
- **
EventHandler
:**用于不需要传递数据的事件。它的签名是void EventHandler(object sender, EventArgs e)(调用时e就给EventArgs.Empty)
。 - **
EventHandler<TEventArgs>
:**用于需要传递数据的事件。TEventArgs
是继承自EventArgs
的类型。
上文代码中事件的定义:
public event EventHandler<DataEventArgs> DataReceived;
- **
EventHandler<DataEventArgs>
:**表示事件处理方法需要接受一个DataEventArgs
类型的参数。
3. 事件与参数的关系
- 在 C# 中,定义事件时必须指定事件处理器(事件处理方法)的委托类型,这个委托类型规定了事件触发时的参数格式,以及订阅该事件的处理器方法的签名。
- **事件触发时传递参数:**当需要主动用代码触发(调用)事件时,需要提供两个参数:
sender
和e
。**sender
:**通常是触发事件的对象(this
)。**e
:**事件数据,类型为EventArgs
或其子类。
- **订阅者访问参数:**所有订阅了该事件的方法(事件处理程序)都会在事件触发时被调用,并接收到触发时传递的参数。
标签:MVP,触发,EventHandler,C#,Presenter,事件,传递数据,Model,DataReceived From: https://www.cnblogs.com/ban-boi-making-dinner/p/18560330