[.NET] Rough Dependency Injection
动机
在设计系统架构的时候,在系统里加入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存放),就可以自行加入相关的实作。
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 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | namespace CLK.Reflection.Implementation { public class ConfigReflectProfileRepository : IReflectProfileRepository { // Default public static string GetDefaultConfigFilename() { // Configuration System.Configuration.Configuration configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); if (configuration == null ) throw new ArgumentNullException(); // ConfigFilename string configFilename = configuration.FilePath; if ( string .IsNullOrEmpty(configFilename) == true ) throw new ArgumentNullException(); // Return return configFilename; } // Fields private readonly string _configFilename = null ; private System.Configuration.Configuration _configuration = null ; // Constructors public ConfigReflectProfileRepository() : this (ConfigReflectProfileRepository.GetDefaultConfigFilename()) { } public ConfigReflectProfileRepository( string configFilename) { #region Require if ( string .IsNullOrEmpty(configFilename) == true ) throw new ArgumentNullException(); #endregion // ConfigFilename _configFilename = configFilename; } // Methods private System.Configuration.Configuration CreateConfiguration() { if (_configuration == null ) { if (_configFilename != null ) { ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap(); configFileMap.ExeConfigFilename = _configFilename; _configuration = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None); } else { _configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); } } return _configuration; } private ConfigReflectProfileSection CreateSection( string reflectSpace) { #region Require if ( string .IsNullOrEmpty(reflectSpace) == true ) throw new ArgumentNullException(); #endregion // Configuration System.Configuration.Configuration configuration = this .CreateConfiguration(); if (configuration == null ) throw new ArgumentException(String.Format( "Action:{0}, State:{1}" , "Reflect" , "Fail to Create Configuration" )); // Section ConfigReflectProfileSection section = configuration.GetSection(reflectSpace) as ConfigReflectProfileSection; if (section == null ) throw new ArgumentException(String.Format( "Action:{0}, State:{1}, ReflectSpace:{2}" , "Reflect" , "Fail to Create ConfigReflectProfileSection" , reflectSpace)); // Return return section; } public string GetDefaultProfileName( string reflectSpace) { #region Require if ( string .IsNullOrEmpty(reflectSpace) == true ) throw new ArgumentNullException(); #endregion // Section ConfigReflectProfileSection section = this .CreateSection(reflectSpace); if (section == null ) throw new MemberAccessException(); // DefaultProfileName string defaultProfileName = section.DefaultProfileName; if ( string .IsNullOrEmpty(defaultProfileName) == true ) throw new ArgumentException(String.Format( "Action:{0}, State:{1}, ReflectSpace:{2}" , "Reflect" , "Fail to Get DefaultProfileName" , reflectSpace)); // Return return defaultProfileName; } public ReflectProfile GetProfile( string reflectSpace, string profileName) { #region Require if ( string .IsNullOrEmpty(reflectSpace) == true ) throw new ArgumentNullException(); #endregion // Result ReflectProfile profile = null ; // Section ConfigReflectProfileSection section = this .CreateSection(reflectSpace); if (section == null ) throw new MemberAccessException(); // Element foreach (ConfigReflectProfileElement element in section.ProfileCollection) { if (element.ProfileName == profileName) { // Profile profile = new ReflectProfile(); profile.ProfileName = element.ProfileName; profile.BuilderType = element.BuilderType; foreach ( string parameterKey in element.UnrecognizedAttributes.Keys) { profile.Parameters.Add(parameterKey, element.UnrecognizedAttributes[parameterKey]); } break ; } } // Return return profile; } public IEnumerable<ReflectProfile> CreateAllProfile( string reflectSpace) { #region Require if ( string .IsNullOrEmpty(reflectSpace) == true ) throw new ArgumentNullException(); #endregion // Result List<ReflectProfile> profileList = new List<ReflectProfile>();; // Section ConfigReflectProfileSection section = this .CreateSection(reflectSpace); if (section == null ) throw new MemberAccessException(); // Element foreach (ConfigReflectProfileElement element in section.ProfileCollection) { // Profile ReflectProfile profile = new ReflectProfile(); profile.ProfileName = element.ProfileName; profile.BuilderType = element.BuilderType; foreach ( string parameterKey in element.UnrecognizedAttributes.Keys) { profile.Parameters.Add(parameterKey, element.UnrecognizedAttributes[parameterKey]); } profileList.Add(profile); } // Return return profileList; } } } |
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 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | namespace CLK.Reflection.Implementation { public sealed class ConfigReflectProfileSection : ConfigurationSection { // Properties [ConfigurationProperty( "default" , DefaultValue = "" , IsRequired = false )] public string DefaultProfileName { get { return ( string ) base [ "default" ]; } set { base [ "default" ] = value; } } [ConfigurationProperty( "" , IsDefaultCollection = true , IsRequired = false )] public ConfigReflectProfileElementCollection ProfileCollection { get { return (ConfigReflectProfileElementCollection) base [ "" ]; } } } public sealed class ConfigReflectProfileElementCollection : ConfigurationElementCollection { // Constructor public ConfigReflectProfileElementCollection() { } // Properties public override ConfigurationElementCollectionType CollectionType { get { return ConfigurationElementCollectionType.AddRemoveClearMap; } } // Methods protected override ConfigurationElement CreateNewElement() { return new ConfigReflectProfileElement(); } protected override object GetElementKey(ConfigurationElement element) { #region Contracts if (element == null ) throw new ArgumentNullException(); #endregion return ((ConfigReflectProfileElement)element).ProfileName; } public void Add(ConfigReflectProfileElement element) { #region Contracts if (element == null ) throw new ArgumentNullException(); #endregion this .BaseAdd(element); } public void Remove( string name) { this .BaseRemove(name); } public bool Contains( string name) { #region Contracts if ( string .IsNullOrEmpty(name) == true ) throw new ArgumentNullException(); #endregion if ( this .BaseGet(name) != null ) { return true ; } else { return false ; } } public ConfigReflectProfileElement GetByName( string name) { #region Contracts if ( string .IsNullOrEmpty(name) == true ) throw new ArgumentNullException(); #endregion return (ConfigReflectProfileElement) this .BaseGet(name); } } public sealed class ConfigReflectProfileElement : UnrecognizedAttributeConfigurationElement { // Constructor public ConfigReflectProfileElement() { // Default this .ProfileName = string .Empty; this .BuilderType = string .Empty; } // Properties [ConfigurationProperty( "name" , IsKey = true , IsRequired = true )] public string ProfileName { get { return Convert.ToString( base [ "name" ]); } set { base [ "name" ] = value; } } [ConfigurationProperty( "builderType" , IsKey = true , IsRequired = false )] public string BuilderType { get { return Convert.ToString( base [ "builderType" ]); } set { base [ "builderType" ] = value; } } } } |
使用
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(); } } } |
期許自己~
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?