【C#|.NET】从控制反转(依赖注入)想到事件注入 (非AOP)
前文
事件注入的想法是由依賴注入所聯想到
依賴注入不算什么吸引人的話題?本篇就不詳說了?不過有閑暇時間的機會不妨按照自己的興趣去摸索、研究一些東西,也是一種樂子。
在抓蟲系列里簡單的描述一下依賴注入在項目中的應用場景抓蟲(五) 淺談依賴注入與控制反轉
關于依賴注入推薦T2噬菌體同學的一篇文章 依賴注入那些事兒
關于事件注入已添加進我的設計模式 【系列索引】結合項目實例 回顧傳統設計模式 打造屬于自己的模式類系列
?概要
所謂事件注入是我一時興起隨便杜撰的詞,其思想借鑒依賴注入。當然看到這個詞很多同學會想到AOP,這里先不置可否。
依賴注入(Dependency Injection),是這樣一個過程:由于某客戶類只依賴于服務類的一個接口,而不依賴于具體服務類,所以客戶類只定義一個注入點。在程序運行過程中,客戶類不直接實例化具體服務類實例,而是客戶類的運行上下文環境或專門組件負責實例化服務類,然后將其注入到客戶類中,保證客戶類的正常運行。
也就是說依賴注入在我們的項目場景中充當一個解耦的角色。在項目結構中它可以解耦出一個模塊、一個服務。T2噬菌體同學在他的博文中描述了一個游戲打怪的例子來解釋依賴注入的作用。那么我們同樣用打怪的例子來闡述下事件注入的場景。
?詳解
關于原型的設計可以參考T2同學對游戲打怪的描述,我這里直接略過看效果圖
下面我們撇開T2同學對依賴注入場景的設計。假設目前這個demo就是可行的。但是隨著游戲版本的更新,招式越來越多,效果越來越絢,規則越來越多。T2同學用依賴注入解決的重點是將Role和武器之間的依賴,封裝算法簇,讓它們之間可以互相替換,讓算法的變化獨立于使用算法的客戶類。
那么浮現問題點,如果我要更新的并不是武器等因素,而是對流程的更新。例如玩dota的同學都知道,一個英雄他的技能前后搖擺的時間也是很重要的因素,好吧,我們給游戲添加技能前搖的設置,砍完怪的我還得獲得金幣,嗯,再添加一下攻擊后獲得金幣的內容。如何符合我們的OCP原則呢。于是,我們引入事件注入的概念。
首先我們來定義我們所需要的行為
/// <summary>/// 攻擊前事件/// </summary>public static event EventHandler<EventArgs> BeforeAttackEvent;protected virtual void BeforeAttack(EventArgs e){EventHandler<EventArgs> tmp = BeforeAttackEvent;if (tmp != null)tmp(this, e);}/// <summary>/// 攻擊后事件/// </summary>public static event EventHandler<GameEventArgs> AttackedEvent;protected virtual void OnAttacked(GameEventArgs e){EventHandler<GameEventArgs> tmp = AttackedEvent;if (tmp != null)tmp(this, e);}?這里定義的僅僅是事件的句柄,如果在這里就實現我們事件的實體也就違背了我們ocp的原則以及事件注入的概念。
這里要提出說明的EventArgs 是包含事件數據的類的基類,如果說我們需要對注入的事件進行額外的信息處理,例如我需要獲得金幣,那么金幣這個屬性需要在事件數據中說明
例如上述的攻擊后事件
/// <summary>/// 注入事件元素/// </summary>public class GameEventArgs :EventArgs{public GameEventArgs(): this(0){}public int Coin {get;set;}public GameEventArgs(int coin){Coin = coin;}}?事件的框架有了,我們便在現有程序中找尋合適的注入點。這里我選擇的是攻擊前后
/// <summary> /// 攻擊怪物 /// </summary> /// <param name="monster">被攻擊的怪物</param> public void Attack(Monster monster){BeforeAttack(EventArgs.Empty);if (monster.HP <= 0){Console.WriteLine("此怪物已死");return;}if ("WoodSword" == WeaponTag){monster.HP -= 20;if (monster.HP <= 0){Console.WriteLine("攻擊成功!怪物" + monster.Name + "已死亡");}else { Console.WriteLine("攻擊成功!怪物" + monster.Name + "損失20HP"); }}else if ("IronSword" == WeaponTag){monster.HP -= 50; if (monster.HP <= 0){Console.WriteLine("攻擊成功!怪物" + monster.Name + "已死亡");}else{Console.WriteLine("攻擊成功!怪物" + monster.Name + "損失50HP");}}else if ("MagicSword" == WeaponTag){Int32 loss = (_random.NextDouble() < 0.5) ? 100 : 200;monster.HP -= loss;if (200 == loss){Console.WriteLine("出現暴擊!!!");}if (monster.HP <= 0){Console.WriteLine("攻擊成功!怪物" + monster.Name + "已死亡");}else{Console.WriteLine("攻擊成功!怪物" + monster.Name + "損失" + loss + "HP");}}else{Console.WriteLine("角色手里沒有武器,無法攻擊!");}var e =new GameEventArgs();OnAttacked(e);}?這些設計完成之后,我們需要的就是設計來注入些什么事件。
[Extension("游戲規則_攻擊前", "1.0.0.0", "熬夜的蟲子")]public class GameRule{public GameRule(){Role.BeforeAttackEvent += BeforeAttack;}void BeforeAttack(object sender, EventArgs e){Console.WriteLine("技能前搖 扭動身體..."); }}?
[Extension("游戲規則_攻擊后", "1.0.0.0", "熬夜的蟲子")]public class GameRule2{private readonly Random _random = new Random();public GameRule2(){Role.AttackedEvent += Attacked;}void Attacked(object sender, EventArgs e){var currentrole = sender as Role;int addcoin = _random.Next(1, 10);if (currentrole != null){currentrole.Coin += addcoin;Console.WriteLine("本次攻擊獲得了..." + addcoin.ToString() + "個金幣,當前金幣為" + currentrole.Coin+"個");}}}?事件定義完成后,我們接下來的步驟就是如何來注入到我們現有的框架中。
老道的同學可以發現在事件定義的過程中,我用了擴展屬性。沒錯,這個屬性就是實現注入環節的樞紐所在。
/// <summary>/// 事件注入實現/// </summary>[AttributeUsage(AttributeTargets.Class)]public class ExtensionAttribute : Attribute{public ExtensionAttribute(string description, string version, string author){_Description = description;_Version = version;_Author = author;}private readonly string _Description;public string Description{get { return _Description; }}private readonly string _Version;public string Version{get { return _Version; }}private readonly string _Author;public string Author{get { return _Author; }}}?如果想更深入的同學可以在設計一個事件注入管理類,添加一些是否可用,過期時間,版本,描述等等信息來管理注入事件。例如當管理類信息入庫,每次注入前check管理類的信息。這樣可以可視化并更方便管理注入的事件。
我們回到注入實現這個話題上來,如何利用這個擴展屬性,通過反射。
var di = new System.IO.DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);foreach (var item in di.GetFiles("*.dll", System.IO.SearchOption.TopDirectoryOnly)){System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFrom(item.FullName);Type[] types = assembly.GetTypes();foreach (Type type in types){object[] attributes = type.GetCustomAttributes(typeof(Extension.ExtensionAttribute), false);foreach (object attribute in attributes){assembly.CreateInstance(type.FullName);}}}?上面的程序更具我們定義的擴展屬性找到相關的注入事件方法類型,并生成實例。到此,一個簡單的注入流程就已經OK了。
我們來看一下效果。
?注入事件的組件與源程序分開,源程序不依賴注入事件組件,可以任意的定義多個同類注入事件,將組件放入程序指定的目錄即可。
例如我們再新建一個注入事件組件
[Extension("游戲規則_攻擊后", "1.0.0.0", "熬夜的蟲子")]public class GameRule{public GameRule(){Role.AttackedEvent += Attacked;}void Attacked(object sender, EventArgs e){Console.WriteLine("技能后擺 O(∩_∩)O哈哈哈~...");}}配置完成后,看下效果?
?本篇到此 希望對大家有幫助
轉載于:https://www.cnblogs.com/dubing/archive/2011/12/21/2295895.html
總結
以上是生活随笔為你收集整理的【C#|.NET】从控制反转(依赖注入)想到事件注入 (非AOP)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: “后 PC”时代,应用为王
- 下一篇: windows 系统监视器 以及建议阀值