前文
依賴注入不算什麼吸引人的話題 不過有閑暇時間的機會不妨按照自己的興趣去摸索、研究一些東西,也是一種樂子。
概要
所謂事件注入是我一時興起随便杜撰的詞,其思想借鑒依賴注入。當然看到這個詞很多同學會想到AOP,這裡先不置可否。
依賴注入(Dependency Injection),是這樣一個過程:由于某客戶類隻依賴于服務類的一個接口,而不依賴于具體服務類,是以客戶類隻定義一個注入點。在程式運作過程中,客戶類不直接執行個體化具體服務類執行個體,而是客戶類的運作上下文環境或專門元件負責執行個體化服務類,然後将其注入到客戶類中,保證客戶類的正常運作。
也就是說依賴注入在我們的項目場景中充當一個解耦的角色。在項目結構中它可以解耦出一個子產品、一個服務。T2噬菌體同學在他的博文中描述了一個遊戲打怪的例子來解釋依賴注入的作用。那麼蟲子同樣用打怪的例子來闡述下事件注入的場景。
詳解
關于原型的設計可以參考T2同學對遊戲打怪的描述,我這裡直接略過看效果圖
下面我們撇開T2同學對依賴注入場景的設計。假設目前這個demo就是可行的。但是随着遊戲版本的更新,招式越來越多,效果越來越絢,規則越來越多。T2同學用依賴注入解決的重點是将Role和武器之間的依賴,封裝算法簇,讓它們之間可以互相替換,讓算法的變化獨立于使用算法的客戶類。
那麼浮現問題點,如果我要更新的并不是武器等因素,而是對流程的更新。例如玩dota的同學都知道,一個英雄他的技能前後搖擺的時間也是很重要的因素,好吧,我們給遊戲添加技能前搖的設定,砍完怪的我還得獲得金币,嗯,再添加一下攻擊後獲得金币的内容。如何符合我們的OCP原則呢。于是,我們引入事件注入的概念。
首先我們來定義我們所需要的行為
<a href="http://blog.51cto.com/dubing/747552#">?</a>
<code>/// <summary></code>
<code> </code><code>/// 攻擊前事件</code>
<code> </code><code>/// </summary></code>
<code> </code><code>public</code> <code>static</code> <code>event</code> <code>EventHandler<EventArgs> BeforeAttackEvent;</code>
<code> </code><code>protected</code> <code>virtual</code> <code>void</code> <code>BeforeAttack(EventArgs e)</code>
<code> </code><code>{</code>
<code> </code><code>EventHandler<EventArgs> tmp = BeforeAttackEvent;</code>
<code> </code><code>if</code> <code>(tmp != </code><code>null</code><code>)</code>
<code> </code><code>tmp(</code><code>this</code><code>, e);</code>
<code> </code><code>}</code>
<code> </code><code>/// <summary></code>
<code> </code><code>/// 攻擊後事件</code>
<code> </code><code>public</code> <code>static</code> <code>event</code> <code>EventHandler<GameEventArgs> AttackedEvent;</code>
<code> </code><code>protected</code> <code>virtual</code> <code>void</code> <code>OnAttacked(GameEventArgs e)</code>
<code> </code><code>EventHandler<GameEventArgs> tmp = AttackedEvent;</code>
這裡定義的僅僅是事件的句柄,如果在這裡就實作我們事件的實體也就違背了我們ocp的原則以及事件注入的概念。
這裡要提出說明的EventArgs 是包含事件資料的類的基類,如果說我們需要對注入的事件進行額外的資訊處理,例如我需要獲得金币,那麼金币這個屬性需要在事件資料中說明
例如上述的攻擊後事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<code>/// 注入事件元素</code>
<code>/// </summary></code>
<code>public</code> <code>class</code> <code>GameEventArgs :EventArgs</code>
<code>{</code>
<code> </code><code>public</code> <code>GameEventArgs()</code>
<code> </code><code>: </code><code>this</code><code>(0)</code>
<code> </code><code>{</code>
<code> </code><code>}</code>
<code> </code><code>public</code> <code>int</code> <code>Coin</code>
<code> </code><code>get</code><code>;</code>
<code> </code><code>set</code><code>;</code>
<code> </code><code>public</code> <code>GameEventArgs(</code><code>int</code> <code>coin)</code>
<code> </code><code>Coin = coin;</code>
<code>}</code>
事件的架構有了,我們便在現有程式中找尋合适的注入點。這裡我選擇的是攻擊前後
<a href="http://blog.51cto.com/dubing/747552#">expand source</a>
這些設計完成之後,我們需要的就是設計來注入些什麼事件。
<code>[Extension(</code><code>"遊戲規則_攻擊前"</code><code>, </code><code>"1.0.0.0"</code><code>, </code><code>"熬夜的蟲子"</code><code>)]</code>
<code> </code><code>public</code> <code>class</code> <code>GameRule</code>
<code> </code><code>public</code> <code>GameRule()</code>
<code> </code><code>{</code>
<code> </code><code>Role.BeforeAttackEvent += BeforeAttack;</code>
<code> </code><code>}</code>
<code> </code><code>void</code> <code>BeforeAttack(</code><code>object</code> <code>sender, EventArgs e)</code>
<code> </code><code>Console.WriteLine(</code><code>"技能前搖 扭動身體..."</code><code>); </code>
<code>[Extension(</code><code>"遊戲規則_攻擊後"</code><code>, </code><code>"1.0.0.0"</code><code>, </code><code>"熬夜的蟲子"</code><code>)]</code>
<code> </code><code>public</code> <code>class</code> <code>GameRule2</code>
<code> </code><code>private</code> <code>readonly</code> <code>Random _random = </code><code>new</code> <code>Random();</code>
<code> </code><code>public</code> <code>GameRule2()</code>
<code> </code><code>Role.AttackedEvent += Attacked;</code>
<code> </code><code>void</code> <code>Attacked(</code><code>object</code> <code>sender, EventArgs e)</code>
<code> </code><code>var</code> <code>currentrole = sender </code><code>as</code> <code>Role;</code>
<code> </code><code>int</code> <code>addcoin = _random.Next(1, 10);</code>
<code> </code><code>if</code> <code>(currentrole != </code><code>null</code><code>)</code>
<code> </code><code>{</code>
<code> </code><code>currentrole.Coin += addcoin;</code>
<code> </code><code>Console.WriteLine(</code><code>"本次攻擊獲得了..."</code> <code>+ addcoin.ToString() + </code><code>"個金币,目前金币為"</code> <code>+ currentrole.Coin+</code><code>"個"</code><code>);</code>
<code> </code><code>}</code>
事件定義完成後,我們接下來的步驟就是如何來注入到我們現有的架構中。
老道的同學可以發現在事件定義的過程中,我用了擴充屬性。沒錯,這個屬性就是實作注入環節的樞紐所在。
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<code> </code><code>/// 事件注入實作</code>
<code> </code><code>/// </summary></code>
<code> </code><code>[AttributeUsage(AttributeTargets.Class)]</code>
<code> </code><code>public</code> <code>class</code> <code>ExtensionAttribute : Attribute</code>
<code> </code><code>public</code> <code>ExtensionAttribute(</code><code>string</code> <code>description, </code><code>string</code> <code>version, </code><code>string</code> <code>author)</code>
<code> </code><code>_Description = description;</code>
<code> </code><code>_Version = version;</code>
<code> </code><code>_Author = author;</code>
<code> </code><code>private</code> <code>readonly</code> <code>string</code> <code>_Description;</code>
<code> </code><code>public</code> <code>string</code> <code>Description</code>
<code> </code><code>get</code> <code>{ </code><code>return</code> <code>_Description; }</code>
<code> </code><code>private</code> <code>readonly</code> <code>string</code> <code>_Version;</code>
<code> </code><code>public</code> <code>string</code> <code>Version</code>
<code> </code><code>get</code> <code>{ </code><code>return</code> <code>_Version; }</code>
<code> </code><code>private</code> <code>readonly</code> <code>string</code> <code>_Author;</code>
<code> </code><code>public</code> <code>string</code> <code>Author</code>
<code> </code><code>get</code> <code>{ </code><code>return</code> <code>_Author; }</code>
如果想更深入的同學可以在設計一個事件注入管理類,添加一些是否可用,過期時間,版本,描述等等資訊來管理注入事件。例如當管理類資訊入庫,每次注入前check管理類的資訊。這樣可以可視化并更友善管理注入的事件。
我們回到注入實作這個話題上來,如何利用這個擴充屬性,通過反射。
<code>var</code> <code>di = </code><code>new</code> <code>System.IO.DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);</code>
<code> </code><code>foreach</code> <code>(</code><code>var</code> <code>item </code><code>in</code> <code>di.GetFiles(</code><code>"*.dll"</code><code>, System.IO.SearchOption.TopDirectoryOnly))</code>
<code> </code><code>{</code>
<code> </code><code>System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFrom(item.FullName);</code>
<code> </code><code>Type[] types = assembly.GetTypes();</code>
<code> </code><code>foreach</code> <code>(Type type </code><code>in</code> <code>types)</code>
<code> </code><code>{</code>
<code> </code><code>object</code><code>[] attributes = type.GetCustomAttributes(</code><code>typeof</code><code>(Extension.ExtensionAttribute), </code><code>false</code><code>);</code>
<code> </code><code>foreach</code> <code>(</code><code>object</code> <code>attribute </code><code>in</code> <code>attributes)</code>
<code> </code><code>{</code>
<code> </code><code>assembly.CreateInstance(type.FullName);</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
上面的程式更具我們定義的擴充屬性找到相關的注入事件方法類型,并生成執行個體。到此,一個簡單的注入流程就已經OK了。
我們來看一下效果。
注入事件的元件與源程式分開,源程式不依賴注入事件元件,可以任意的定義多個同類注入事件,将元件放入程式指定的目錄即可。
例如我們再建立一個注入事件元件
<code> </code><code>public</code> <code>class</code> <code>GameRule</code>
<code> </code><code>public</code> <code>GameRule()</code>
<code> </code><code>Role.AttackedEvent += Attacked;</code>
<code> </code><code>void</code> <code>Attacked(</code><code>object</code> <code>sender, EventArgs e)</code>
<code> </code><code>Console.WriteLine(</code><code>"技能後擺 O(∩_∩)O哈哈哈~..."</code><code>);</code>
配置完成後,看下效果
本篇到此 希望對大家有幫助
需要源碼的同學可以留個郵箱
本文轉自 熬夜的蟲子 51CTO部落格,原文連結:http://blog.51cto.com/dubing/747552