动机
在设计系统架构的时候,在系统里加入Dependency Injection(DI),让系统可以在不改变程序代码的状况下,抽换类别来提高重用性、扩充性。在.NET里可以选择一些的Framework来使用,例如:Spring Framework、Unity Application Block、Managed Extensibility Framework (MEF)。
在一些中小型项目,套用上列这些Framework,常常会有种拿大炮打蚊子的感觉。因为这些Framework为了能够符合更多的使用情景,而加入了很多功能。一直加下去的结果,就是系统变的庞大并且较难理解上手。以Spring来说,光是怎么写配置文件的操作,都可以写成书了。当然这样用意是好的,一个够强大的Framework学习曲线必然会较高,但是学会之后能适用的范围也会更广。
不过不得面对的现实是,很多开发人员没时间学习各种Framework(或是无心学习?)。在系统架构里加入这些强大Framework,提高了系统的各项质量时,也拉高技术门坎。过高的技术门坎在后续开发、维护,补充人手时会越来越艰难…。以此为发想于是就萌生了:建立简单易用的一组类别,完成Dependency Injection(DI)应该具备的基础功能。只需要学习基础功能,就能为系统加入Dependency Injection(DI)功能。这样就能降低开发人员的技术门坎,让更多的开发人员能做为补充人力加入项目。
本篇文章介绍一个实作Dependency Injection(DI)基础功能的Rough Dependency Injection实作,这个实作定义对象之间的职责跟互动,用来反射生成要注入的对象。为自己做个纪录,也希望能帮助到有需要的开发人员。
* 必须要特别声明的是,Spring、MEF这些Framework有很高的价值。当这些Framework成为整个团队基础开发知识时,整个团队的开发能力将会提升一个台阶。
结构
Rough Dependency Injection主要是将Dependency Injection(DI) 基础功能,拆为两大部分:对象生成、对象设定,并且将复杂的对象设定隔离在系统之外。模式的结构如下:
主要的参与者有:
TEntity
-欲注入系统的对象。
IReflectProfileRepository
- ReflectProfile进出系统边界的接口。
-将对象设定隔离在系统之外,可以抽换各种不同数据存储。
-极端的案例可以抽换成为HardCodeRepository,连设定都从系统移除。
ReflectProfile
-DTO物件。
-储存用来反射生成IReflectBuilder所需要的参数。
IReflectBuilder
-经由ReflectProfile储存参数,反射生成的对象接口。
-使用ReflectProfile储存参数,生成TEntity。
ReflectManager
-藉由IReflectProfileRepository取得系统储存的ReflectProfile。
-使用ReflectProfile储存参数,反射生成IReflectBuilder。
-使用IReflectBuilder与ReflectProfile,生成TEntity。
透过下面的图片说明,可以了解相关对象之间的互动流程。
实作
范列下载
实作说明请参照范例程序内容:RoughDependencyInjectionSample点此下载
ReflectProfile、IReflectProfileRepository
首先为了将对象设定这个职责,隔离在系统之外。所以在整个模块的边界套用Repository模式,建立出IReflectProfileRepository。并且使用ReflectProfile做为进出系统边界的DTO对象,这个ReflectProfile对象储存生成对象的参数数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
namespace CLK.Reflection { public sealed class ReflectProfile { // Constructors public ReflectProfile() { // Default this .ProfileName = string .Empty; this .BuilderType = string .Empty; this .Parameters = new Dictionary< string , string >(); } // Properties public string ProfileName { get ; set ; } public string BuilderType { get ; set ; } public Dictionary< string , string > Parameters { get ; private set ; } } } |
1
2
3
4
5
6
7
8
9
10
11
12
|
namespace CLK.Reflection { public interface IReflectProfileRepository { // Methods string GetDefaultProfileName( string reflectSpace); ReflectProfile GetProfile( string reflectSpace, string profileName); IEnumerable<ReflectProfile> CreateAllProfile( string reflectSpace); } } |
IReflectBuilder、ReflectManager
接着处理ReflectManager来使用ReflectProfile。ReflectManager主要的工作就是透过IReflectProfileRepository取得的ReflectProfile,用ReflectProfile来反射生成IReflectBuilder实作。然后在利用这个IReflectBuilder实作,配合ReflectProfile来生成系统需要注入的TEntity。之所以不直接反射生成TEntity另外再建一层IReflectBuilder,是因为不希望在TEntity里,混入DI相关功能的相依。
1
2
3
4
5
6
7
8
|
namespace CLK.Reflection { public interface IReflectBuilder { // Methods object Create(Dictionary< string , string > parameters); } } |
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
namespace CLK.Reflection { public class ReflectManager { // Fields private readonly IReflectProfileRepository _repository = null ; // Constructors public ReflectManager(IReflectProfileRepository repository) { #region Contracts if (repository == null ) throw new ArgumentNullException(); #endregion // Arguments _repository = repository; } // Methods private TEntity Create<TEntity>(ReflectProfile profile) where TEntity : class { #region Contracts if (profile == null ) throw new ArgumentNullException(); #endregion // Require if ( string .IsNullOrEmpty(profile.ProfileName) == true ) throw new InvalidOperationException(); if ( string .IsNullOrEmpty(profile.BuilderType) == true ) throw new InvalidOperationException(); // BuilderType Type builderType = Type.GetType(profile.BuilderType); if (builderType == null ) throw new ArgumentException(String.Format( "Action:{0}, State:{1}, BuilderType:{2}" , "Reflect" , "Fail to Access BuilderType" , profile.BuilderType)); // Builder IReflectBuilder builder = Activator.CreateInstance(builderType) as IReflectBuilder; if (builder == null ) throw new ArgumentException(String.Format( "Action:{0}, State:{1}, BuilderType:{2}" , "Reflect" , "Fail to Create Builder" , profile.BuilderType)); // Entity TEntity entity = builder.Create(profile.Parameters) as TEntity; if (entity == null ) throw new ArgumentException(String.Format( "Action:{0}, State:{1}, BuilderType:{2}" , "Reflect" , "Fail to Create Entity" , profile.BuilderType)); // Return return entity; } public IEnumerable<TEntity> CreateAll<TEntity>( string reflectSpace) where TEntity : class { #region Contracts if ( string .IsNullOrEmpty(reflectSpace) == true ) throw new ArgumentNullException(); #endregion // Result List<TEntity> entityList = new List<TEntity>(); // Create foreach (ReflectProfile profile in _repository.CreateAllProfile(reflectSpace)) { TEntity entity = this .Create<TEntity>(profile); if (entity != null ) { entityList.Add(entity); } } // Return return entityList; } public TEntity Create<TEntity>( string reflectSpace) where TEntity : class { #region Contracts if ( string .IsNullOrEmpty(reflectSpace) == true ) throw new ArgumentNullException(); #endregion // ProfileName string profileName = _repository.GetDefaultProfileName(reflectSpace); if ( string .IsNullOrEmpty(profileName) == true ) throw new ArgumentException(String.Format( "Action:{0}, State:{1}, ReflectSpace:{2}" , "Reflect" , "Fail to Get DefaultProfileName" , reflectSpace)); // Return return this .Create<TEntity>(reflectSpace, profileName); } public TEntity Create<TEntity>( string reflectSpace, string profileName) where TEntity : class { #region Contracts if ( string .IsNullOrEmpty(reflectSpace) == true ) throw new ArgumentNullException(); if ( string .IsNullOrEmpty(profileName) == true ) throw new ArgumentNullException(); #endregion // Profile ReflectProfile profile = _repository.GetProfile(reflectSpace, profileName); if (profile == null ) return default (TEntity); // Return return this .Create<TEntity>(profile); } } } |
ConfigReflectProfileRepository
在范例程序里,示范了IReflectProfileRepository的实作,这个实作使用App.config做为ReflectProfile的数据源。相关的程序代码如下,有兴趣的开发人员可以花点时间学习,在需要扩充IReflectProfileRepository的时候(例如:改用SQL存放),就可以自行加入相关的实作。
使用
DisplayWorker
接着撰写一个虚拟的IDisplayWorker来示范如何套用Rough Dependency Injection。首先在项目内建立IDisplayWorker,这个IDisplayWorker很简单的只开放一个Show函式让系统使用。接着建立两个实作IDisplayWorker的对象,这两个对象就是后续要用来注入系统的对象。到这边可以发现,套用Rough Dependency Injection注入的对象,不会有额外的相依,只要完成自己应有的职责就可以。
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
|
namespace TestProject { public interface IDisplayWorker { // Methods void Show(); } public class AAADisplayWorker : IDisplayWorker { // Properties public string AAA { get ; set ; } // Methods public void Show() { Console.WriteLine( this .AAA); } } public class BBBDisplayWorker : IDisplayWorker { // Properties public int BBB { get ; set ; } // Methods public void Show() { Console.WriteLine( this .BBB); } } } |
DisplayWorkerBuilder
要让注入对象,不会有额外的相依,也是要付出代价。要另外建立一层Builder,用来生成注入对象,以及隔离注入对象与Rough Dependency Injection的相依。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
namespace TestProject { public class AAADisplayWorkerBuilder : IReflectBuilder { // Methods public object Create(Dictionary< string , string > parameters) { AAADisplayWorker worker = new AAADisplayWorker(); worker.AAA = Convert.ToString(parameters[ "AAA" ]); return worker; } } public class BBBDisplayWorkerBuilder : IReflectBuilder { // Methods public object Create(Dictionary< string , string > parameters) { BBBDisplayWorker worker = new BBBDisplayWorker(); worker.BBB = Convert.ToInt32(parameters[ "BBB" ]); return worker; } } } |
执行
最后建立使用IDisplayWorker的范例RoughDependencyInjectionSample,在RoughDependencyInjectionSample内透过ReflectManager搭配App.config里的设定,为系统注入两个IDisplayWorker实作让系统使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<? xml version = "1.0" encoding = "utf-8" ?> < configuration > <!-- ConfigSections --> < configSections > < sectionGroup name = "testProject" > < section name = "displayWorker" type = "CLK.Reflection.Implementation.ConfigReflectProfileSection, CLK" /> </ sectionGroup > </ configSections > <!-- TestProject --> < testProject > < displayWorker > < add name = "AAA" builderType = "TestProject.AAADisplayWorkerBuilder, TestProject" AAA = "Clark=_=y-~" /> < add name = "BBB" builderType = "TestProject.BBBDisplayWorkerBuilder, TestProject" BBB = "1234" /> </ displayWorker > </ testProject > </ configuration > |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
namespace TestProject { class Program { static void Main( string [] args) { // ReflectManager ReflectManager reflectManager = new ReflectManager( new ConfigReflectProfileRepository()); // CreateAll foreach (IDisplayWorker worker in reflectManager.CreateAll<IDisplayWorker>( @"testProject/displayWorker" )) { // Show worker.Show(); } // End Console.ReadLine(); } } } |
期許自己~
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。