所謂「事件注入」是借鑑依賴注入(DI)思想臨時提出的概念。DI 讓客戶類僅依賴服務介面,由執行期上下文或容器實例化具體服務並注入,實現解耦。事件注入則將這一思想應用於**流程擴展點**:在核心業務流程的關鍵節點(如前置/後置)定義事件,由外部組件透過反射動態訂閱並注入行為,實現**開放封閉原則(OCP)**下的流程無侵入式擴展。
假設基礎攻擊邏輯已穩定,後續需求頻變:新增「技能前搖」、「擊殺獲得金幣」等橫切關注點。若直接修改核心 Attack 方法違反 OCP。事件注入提供優雅方案:
// 攻擊前事件(無額外數據)
public static event EventHandler<EventArgs> BeforeAttackEvent;
protected virtual void BeforeAttack(EventArgs e) {
BeforeAttackEvent?.Invoke(this, e);
}
// 攻擊後事件(攜帶金幣數據)
public static event EventHandler<GameEventArgs> AttackedEvent;
protected virtual void OnAttacked(GameEventArgs e) {
AttackedEvent?.Invoke(this, e);
}
// 自訂事件參數
public class GameEventArgs : EventArgs {
public int Coin { get; set; }
public GameEventArgs(int coin = 0) { Coin = coin; }
}
事件定義處**僅宣告委派**,不實作具體邏輯,確保核心碼純淨。
public void Attack(Monster monster) {
BeforeAttack(EventArgs.Empty); // 注入點 1:攻擊前
// ... 原有攻擊邏輯(武器判斷、傷害計算、死亡判定)...
var e = new GameEventArgs(); // 注入點 2:攻擊後
OnAttacked(e);
}
[Extension("遊戲規則_攻擊前", "1.0.0.0", "作者")]
public class GameRule_BeforeAttack {
public GameRule_BeforeAttack() {
Role.BeforeAttackEvent += BeforeAttack;
}
void BeforeAttack(object sender, EventArgs e) {
Console.WriteLine("技能前搖 扭動身體...");
}
}
[Extension("遊戲規則_攻擊後", "1.0.0.0", "作者")]
public class GameRule_AfterAttack {
private readonly Random _rnd = new Random();
public GameRule_AfterAttack() {
Role.AttackedEvent += Attacked;
}
void Attacked(object sender, GameEventArgs e) {
var role = sender as Role;
int coin = _rnd.Next(1, 10);
role.Coin += coin;
Console.WriteLine($"獲得 {coin} 金幣,總計 {role.Coin}");
}
}
擴展類別透過建構子自動訂閱靜態事件,**與核心程式碼零耦合**,可獨立編譯部署為 DLL。
[AttributeUsage(AttributeTargets.Class)]
public class ExtensionAttribute : Attribute {
public string Description { get; }
public string Version { get; }
public string Author { get; }
public ExtensionAttribute(string desc, string ver, string auth) {
Description = desc; Version = ver; Author = auth;
}
}
// 啟動時掃描目錄下所有 DLL,實例化標記了 ExtensionAttribute 的類別
var dir = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
foreach (var file in dir.GetFiles("*.dll")) {
var asm = Assembly.LoadFrom(file.FullName);
foreach (var type in asm.GetTypes()) {
var attrs = type.GetCustomAttributes(typeof(ExtensionAttribute), false);
foreach (var attr in attrs) {
asm.CreateInstance(type.FullName); // 觸發建構子完成訂閱
}
}
}
啟動程式,攻擊怪物,控制台輸出:
技能前搖 扭動身體... 攻擊成功!怪物哥布林損失 20 HP 本次攻擊獲得了... 7 個金幣, 當前金幣為 7 個
若新增「技能後擺」規則 DLL 並重啟,輸出自動變為:
技能前搖 扭動身體... 攻擊成功!怪物哥布林損失 20 HP 技能後擺 O(∩_∩)O 哈哈哈~... 本次攻擊獲得了... 3 個金幣, 當前金幣為 10 個
核心代碼**完全未修改**,完美實現 OCP 原則下的流程級擴展。