场景
刚写完一个干净利落的方法,比如保存数据到数据库,逻辑清晰、结构优雅,
第二天,“嘿,保存完数据,记得给客户发个邮件哦~”
第三天,“能不能再发个消息通知其他系统?”
第四天,“能不能记录一下操作日志?”
第五天,“再加个短信提醒吧。”
……
就这样,原本清清爽爽的 SaveData 方法,变成了一个臃肿不堪的函数:
我们管这种代码叫 “脚本代码”或“面条代码” —— 逻辑缠在一起,改一处,处处提心吊胆。
C# 提供了更灵活的方式来处理这种场景,那就是利用 Attribute 来对业务进行解耦,从而避免这种脚本式的代码,提高代码的可扩展性
1. 定义特性
namespace WebApplication2.Attributes
{/// <summary>/// https://mp.weixin.qq.com/s/Sd9q7FOTlk29wBknNQh87w/// 后置操作特性基类/// 所有继承它的特性都可以用在方法上,允许多个,不继承到子类/// </summary>[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]public abstract class PostOperationAttribute : Attribute{// 每个后置操作都必须实现 Execute 方法public abstract void Execute(object returnValue);}/// <summary>/// 发送邮件特性/// </summary>public class SendEmailAttribute : PostOperationAttribute{private readonly string _emailTemplate; // 邮件模板名称// 构造函数接收模板名public SendEmailAttribute(string emailTemplate){_emailTemplate = emailTemplate;}// 实现具体的发送邮件逻辑public override void Execute(object returnValue){// 实际项目中这里应该调用邮件服务Console.WriteLine($"发送邮件 - 使用模板: {_emailTemplate}");Console.WriteLine($"邮件内容包含数据: {returnValue}");}}/// <summary>/// 发送消息特性/// </summary>public class SendMessageAttribute : PostOperationAttribute{private readonly string _messageType; // 消息类型public SendMessageAttribute(string messageType){_messageType = messageType;}public override void Execute(object returnValue){Console.WriteLine($"发送 {_messageType} 消息");Console.WriteLine($"消息内容包含数据: {returnValue}");}}
}
2. 编写业务方法
using System.Reflection;namespace WebApplication2.Attributes
{public class DataService{/// <summary>/// 核心逻辑只负责保存数据/// 使用特性标记需要后置处理的方法/// </summary>/// <param name="data"></param>/// <returns></returns>[SendEmail("DataSavedTemplate")][SendMessage("Notification")]public virtual int SaveData(string data){// 这里只关注保存数据的核心业务逻辑Console.WriteLine($"保存数据: {data}");// 模拟返回保存后的IDreturn new Random().Next(1000);}}
}
3. 创建拦截类(代理)
using System.Reflection;namespace WebApplication2.Attributes
{/// <summary>/// 自动处理 Attribute 的代理类/// </summary>public class DataServiceProxy : DataService{public override int SaveData(string data){// 调用基类方法var result = base.SaveData(data);// 获取方法信息MethodInfo methodInfo = typeof(DataService).GetMethod("SaveData");// 获取该方法上所有的 PostOperationAttribute 特性实例var postOps = methodInfo.GetCustomAttributes<PostOperationAttribute>(true);// 遍历并执行每一个后置操作foreach (var op in postOps){op.Execute(result);}return result;}}
}
4. 使用
using Microsoft.AspNetCore.Mvc;
using WebApplication2.Attributes;namespace WebApplication2.Controllers
{[Route("api/Attributes/[action]")][ApiController]public class AttributesController : ControllerBase{[HttpGet]public string Test(){// 使用代理类而不是直接使用DataServicevar dataService = new DataServiceProxy();// 直接调用方法,后置操作会自动执行int savedId = dataService.SaveData("测试数据");Console.WriteLine($"保存成功,ID: {savedId}");return "";}}
}
5. 运行和测试
6.总结
● 核心业务方法不再被新增加的业务需求污染
● 扩展功能就像搭积木一样快捷方便
● 新增功能无需修改原有代码,维护成本大大降低
● 一眼就能看出某个方法执行后会触发哪些操作,代码可读性更强