場景
剛寫完一個干凈利落的方法,比如保存數據到數據庫,邏輯清晰、結構優雅,
第二天,“嘿,保存完數據,記得給客戶發個郵件哦~”
第三天,“能不能再發個消息通知其他系統?”
第四天,“能不能記錄一下操作日志?”
第五天,“再加個短信提醒吧。”
……
就這樣,原本清清爽爽的 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.總結
● 核心業務方法不再被新增加的業務需求污染
● 擴展功能就像搭積木一樣快捷方便
● 新增功能無需修改原有代碼,維護成本大大降低
● 一眼就能看出某個方法執行后會觸發哪些操作,代碼可讀性更強