• [.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对象储存生成对象的参数数据。

    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; }
        }
    }
    
    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相关功能的相依。

    namespace CLK.Reflection
    {
        public interface IReflectBuilder
        {
            // Methods
            object Create(Dictionary<string, string> parameters);
        }
    }
    
    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存放),就可以自行加入相关的实作。

    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;
            }
        }
    }
    
    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注入的对象,不会有额外的相依,只要完成自己应有的职责就可以。

    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的相依。

    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实作让系统使用。

    <?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>
    
    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();
            }
        }
    }
    


  • 相关阅读:
    webpack.DefinePlugin
    webpack-dev-server配置指南(使用webpack3.0)
    Eclipse配色方案插件
    解决Sublime Text 3中文显示乱码问题(转)
    Java连接SqlServer2008数据库
    [转]java中判断字符串是否为数字的三种方法
    VS2008 SP1 安装卡在 VS90sp1-KB945140-X86-CHS的解决方法
    Python获取桌面路径
    关于fdisk命令
    socket 错误之:OSError: [WinError 10057] 由于套接字没有连接并且(当使用一个 sendto 调用发送数据报套接字时)没有提供地址,发送或接收数据的请求没有被接受。
  • 原文地址:https://www.cnblogs.com/clark159/p/2684208.html
Copyright © 2020-2023  润新知