博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【C#|.NET】从控制反转(依赖注入)想到事件注入(非AOP)
阅读量:6887 次
发布时间:2019-06-27

本文共 4346 字,大约阅读时间需要 14 分钟。

前文

关于依赖注入推荐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 是包含事件数据的类的基类,如果说我们需要对注入的事件进行额外的信息处理,例如我需要获得金币,那么金币这个属性需要在事件数据中说明

例如上述的攻击后事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// <summary>
/// 注入事件元素
/// </summary>
public 
class 
GameEventArgs :EventArgs
{
    
public 
GameEventArgs()
        
this
(0)
    
{
    
}
 
    
public 
int 
Coin
    
{
        
get
;
        
set
;
    
}
 
    
public 
GameEventArgs(
int 
coin)
    
{
        
Coin = coin;
    
}
}

 事件的框架有了,我们便在现有程序中找寻合适的注入点。这里我选择的是攻击前后

 这些设计完成之后,我们需要的就是设计来注入些什么事件。

[Extension(
"游戏规则_攻击前"
"1.0.0.0"
"熬夜的虫子"
)]
    
public 
class 
GameRule
    
{
        
public 
GameRule()
        
{
            
Role.BeforeAttackEvent += BeforeAttack;
        
}
 
        
void 
BeforeAttack(
object 
sender, EventArgs e)
        
{
           
Console.WriteLine(
"技能前摇 扭动身体..."
);              
        
}
    
}

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[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+
"个"
);
            
}
        
}
    
}

 事件定义完成后,我们接下来的步骤就是如何来注入到我们现有的框架中。

老道的同学可以发现在事件定义的过程中,我用了扩展属性。没错,这个属性就是实现注入环节的枢纽所在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/// <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管理类的信息。这样可以可视化并更方便管理注入的事件。

我们回到注入实现这个话题上来,如何利用这个扩展属性,通过反射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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了。

我们来看一下效果。

 注入事件的组件与源程序分开,源程序不依赖注入事件组件,可以任意的定义多个同类注入事件,将组件放入程序指定的目录即可。

例如我们再新建一个注入事件组件

1
2
3
4
5
6
7
8
9
10
11
12
13
[Extension(
"游戏规则_攻击后"
"1.0.0.0"
"熬夜的虫子"
)]
        
public 
class 
GameRule
        
{
            
public 
GameRule()
            
{
                
Role.AttackedEvent += Attacked;
            
}
 
            
void 
Attacked(
object 
sender, EventArgs e)
            
{
                
Console.WriteLine(
"技能后摆 O(∩_∩)O哈哈哈~..."
);
            
}
        
}

配置完成后,看下效果

 


 本篇到此 希望对大家有帮助

需要源码的同学可以留个邮箱

本文转自 熬夜的虫子  51CTO博客,原文链接:http://blog.51cto.com/dubing/747552

转载地址:http://ljabl.baihongyu.com/

你可能感兴趣的文章
node环境搭建
查看>>
Speed ScrollView
查看>>
BJImageCropper
查看>>
android handler总结
查看>>
2. ASIHttpRequest-发送数据
查看>>
[应用模板]移动应用界面
查看>>
嵌入式Linux C编程 02
查看>>
sql server支持连接管理功能
查看>>
java的强制类型转换想到的
查看>>
简要介绍cookie与session的区别与联系
查看>>
mysql flush用法
查看>>
response.setHeader()的用法
查看>>
一位前辈的经验,给正在思考的自己
查看>>
分享一篇关于lucene原理的文章
查看>>
基于 HTML5 结合互联网+ 的 3D 隧道
查看>>
Win10下 80端口被system(pid=4)占用的解决方法
查看>>
使用SubVersion+TortoiseSVN多仓库方式进行版本控制
查看>>
Nginx虚拟目录alias和root目录
查看>>
MySQL(Extends)
查看>>
Android KeyboardView实现App内置键盘开发
查看>>