【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哈哈哈~...");
            }
        }

配置完成後,看下效果

 


 本篇到此 希望對大家有幫助

需要源碼的同學可以留個郵箱

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章