在同一配置中解耦配置子组件

时间:2015-11-10 15:13:32

标签: c# generics interface decoupling

我的配置类出现以下问题:

让我说我有以下几点:

interface IConfiguration 
{
    IEnumerable<IItemConfiguration> ItemConfigurations { get; }
}

interface IItemConfiguration 
{
    IDbConfiguration DbConfiguration { get; }
    INotificationConfiguration NotificationConfiguration { get; }
}

interface IDbConfiguration 
{
    string ConnectionString { get; }
}

interface INotificationConfiguration 
{
    IEmailConfiguration EmailConfiguration { get; }
    IPhoneConfiguration PhoneConfiguration { get; }
}

interface IEmailConfiguration 
{
    //primitive stuff here
}

interface IPhoneConfiguration 
{
    //primitive stuff here
}

interface ISmsConfiguration 
{
    //primitive stuff here
}

现在,我有一个单独的库,我使用Xml实现这些接口。这个例子适用于所有接口,我有一个初始化方法,它接受一个XElement,从中读取所有元素。

class XmlNotificationConfiguration : INotificationConfiguration
{
    private IEmailConfiguration _emailConfiguration;
    public IEmailConfiguration EmailConfiguration 
    {
        get { return _emailConfiguration; }
    }

    private IPhoneConfiguration _phoneConfiguration;
    public IPhoneConfiguration PhoneConfiguration 
    {
        get { return _phoneConfiguration; }
    }

    public bool Initialize(XElement xElement) 
    {
        //parse xElement for the XElement representing the EmailConfiguration
        //var emailConfigElement = ...

        //parse xElement for the XElement representing the PhoneConfiguration
        //var phoneConfigElement = ...

        //Coupling here !!!
        _emailConfiguration = new XmlEmailConfiguration();
        _emailConfiguration.Initialize(emailConfigElement);

        //Coupling here !!!
        _phoneConfiguration = new XmlPhoneConfiguration();
        _phoneConfiguration.Initialize(phoneConfigElement);

        return true;
    }
}

所以基本上我已经模拟了所有配置,以便能够一次使用xml配置,可能是数据库一个,也许是csv一个等等。

但问题是所有这些实现都是耦合的。如果我选择了xml配置,我最终会将所有内部类(EmailConfiguration,DbConfiguration,PhoneConfiguration等)都设置为xml。 我想要的是能够从xml读取EmailConfiguration,从db读取PhoneConfiguration,从csv读取PhoneConfiguration。

我遇到的主要问题是Initialize方法。对于xml实现,它将XElement作为参数,对于csv实现,它可能需要一行,对于db实现,它可能需要连接字符串,用户ID等,因此不同的对象。如果它是相同的参数,那将非常简单:只需将Initialize添加到配置对象的接口,并为每个配置提供工厂,传递这些工厂并创建对象。不幸的是,这不适用。

到目前为止我有2个解决方案:

i)使所有参数类(XElement,csv line等)实现一个接口,在这个接口中,他们都可以通过带字符串值的字典提供内部数据,然后我可以根据组件实现反序列化(反序列化为我的Xml库中的XElement,反序列化到我的csv库中的csv行等)。然后我将传递工厂,这些工厂将根据所有参数类实现的一些IParameter接口创建我的组件。这似乎非常hackish,也容易出现运行时错误。

ii)走仿制方式: 也使用工厂,但通用工厂。但这真的很奇怪,接口看起来像:

interface IConfiguration<T, Q, R>
{
    bool Initialize(T emailParameters, Q phoneParameters, R dbParameters);
    IEnumerable<IItemConfiguration<T,Q,R>> ItemConfigurations { get; }
}

interface IItemConfiguration<T, Q, R> 
{
    bool Initialize(T emailParameters, Q phoneParameters, R dbParameters);
    IDbConfiguration<R> DbConfiguration { get; }
    INotificationConfiguration<T, Q> NotificationConfiguration { get; }
}

interface IDbConfiguration<T>
{
    bool Initialize(T parameters);
    string ConnectionString { get; }
}

interface INotificationConfiguration<T, Q>
{
    bool Initialize(T emailParameters, Q phoneParameters);
    IEmailConfiguration<T> EmailConfiguration { get; }
    IPhoneConfiguration<Q> PhoneConfiguration { get; }
}

interface IEmailConfiguration<T>
{
    bool Initialize(T parameters);
    //primitive stuff here
}

interface IPhoneConfiguration<T>
{
    bool Initialize(T parameters);
    //primitive stuff here
}

interface ISmsConfiguration<T> 
{
    bool Initialize(T parameters);
    //primitive stuff here
}

然后我可以再次为所有组件传递工厂,但这次是通用工厂。 由于两个原因,这似乎很难看:

a)有时候,例如在INotificationConfiguration中,如果我想从xml中接收电子邮件和电话,则T和Q将是相同的类型。这似乎是多余的。

b)如果我有越来越多的嵌套接口(我将拥有)用于配置,我最终会得到10-15个泛型。

所以问题是,什么是最好的解决方案。我希望所有代码都清楚,如果不是,我会更新它。

LE:我希望我有一个最清晰的例子:

using Ninject;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var notificationConfigType = ConfigType.Xml;
            var commonObject = "some Common object";
            var emailConfigType = ConfigType.Csv;
            var phoneConfigType = ConfigType.Xml;

            IKernel kernel = new StandardKernel(new [] { new InitModule()});
            kernel.Load(Assembly.GetExecutingAssembly());
            var builder = kernel.Get<INotificationConfigurationBuilder>();
            var notificationConfig = builder.Build(notificationConfigType, commonObject, emailConfigType, phoneConfigType);
        }
    }

    enum ConfigType
    {
        Csv,
        Xml
    }

    interface INotificationConfiguration
    {
        bool Initialize(string commonObject, ConfigType emailConfigType, ConfigType phoneConfigType);
        IEmailConfiguration EmailConfiguration { get; }
        IPhoneConfiguration PhoneConfiguration { get; }
    }

    interface IEmailConfiguration
    {
        bool Initialize(string commonObject);
        //primitives...
    }

    class XmlEmailConfiguration : IEmailConfiguration
    {

        public bool Initialize(string commonObject)
        {
            //do the actual parsing here
            return true;
        }
    }

    class CsvEmailConfiguration : IEmailConfiguration
    {

        public bool Initialize(string commonObject)
        {
            //do the actual parsing here
            return true;
        }
    }

    interface IPhoneConfiguration
    {
        bool Initialize(string commonObject);
        //primitive stuff here
    }

    class XmlPhoneConfiguration : IPhoneConfiguration
    {
        public bool Initialize(string commonObject)
        {
            //do the actual parsing here
            return true;
        }
    }

    class CsvPhoneConfiguration : IPhoneConfiguration
    {
        public bool Initialize(string commonObject)
        {
            //do the actual parsing here
            return true;
        }
    }

    class XmlNotificationConfiguration : INotificationConfiguration
    {
        private IEmailConfigurationBuilder _emailBuilder;
        private IPhoneConfigurationBuilder _phoneBuilder;

        public XmlNotificationConfiguration(IEmailConfigurationBuilder emailBuilder, IPhoneConfigurationBuilder phoneBuilder)
        {
            _emailBuilder = emailBuilder;
            _phoneBuilder = phoneBuilder;
        }

        private IEmailConfiguration _emailConfiguration;
        public IEmailConfiguration EmailConfiguration
        {
            get { return _emailConfiguration; }
        }

        private IPhoneConfiguration _phoneConfiguration;
        public IPhoneConfiguration PhoneConfiguration
        {
            get { return _phoneConfiguration; }
        }

        public bool Initialize(string commonObject, ConfigType emailConfigType, ConfigType phoneConfigType)
        {
            //normally this would be a processing of the commonObject
            var emailCommonObject = "abc";

            //normally this would be a processing of the commonObject
            var phoneCommonObject = "drf";

            _emailConfiguration = _emailBuilder.Build(emailConfigType, emailCommonObject);
            _phoneConfiguration = _phoneBuilder.Build(phoneConfigType, phoneCommonObject);

            return true;
        }
    }

    class CsvNotificationConfiguration : INotificationConfiguration 
    {
        private IEmailConfigurationBuilder _emailBuilder;
        private IPhoneConfigurationBuilder _phoneBuilder;

        public CsvNotificationConfiguration(IEmailConfigurationBuilder emailBuilder, IPhoneConfigurationBuilder phoneBuilder)
        {
            _emailBuilder = emailBuilder;
            _phoneBuilder = phoneBuilder;
        }

        public bool Initialize(string commonObject, ConfigType emailConfigType, ConfigType phoneConfigType)
        {
            throw new NotImplementedException();
        }

        public IEmailConfiguration EmailConfiguration
        {
            get { throw new NotImplementedException(); }
        }

        public IPhoneConfiguration PhoneConfiguration
        {
            get { throw new NotImplementedException(); }
        }
    }

    interface IEmailConfigurationBuilder
    {
        IEmailConfiguration Build(ConfigType type, string commonObject);
    }

    class EmailConfigurationBuilder : IEmailConfigurationBuilder 
    {
        public IEmailConfiguration Build(ConfigType type, string commonObject)
        {
            switch (type)
            {
                case ConfigType.Csv:
                    var config = new CsvEmailConfiguration();
                    config.Initialize(commonObject);
                    return config;
                case ConfigType.Xml:
                    var cfg = new XmlEmailConfiguration();
                    cfg.Initialize(commonObject);
                    return cfg;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }

    interface IPhoneConfigurationBuilder
    {
        IPhoneConfiguration Build(ConfigType type, string commonObject);
    }

    class PhoneConfigurationBuilder : IPhoneConfigurationBuilder
    {
        public IPhoneConfiguration Build(ConfigType type, string commonObject)
        {
            switch (type)
            {
                case ConfigType.Csv:
                    var config = new CsvPhoneConfiguration();
                    config.Initialize(commonObject);
                    return config;
                case ConfigType.Xml:
                    var cfg = new XmlPhoneConfiguration();
                    cfg.Initialize(commonObject);
                    return cfg;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }

    interface INotificationConfigurationBuilder
    {
        INotificationConfiguration Build(ConfigType type, string commonObject, ConfigType emailConfigType, ConfigType phoneConfigType);
    }

    class NotificationConfigurationBuilder : INotificationConfigurationBuilder
    {
        private IEmailConfigurationBuilder _emailBuilder;
        private IPhoneConfigurationBuilder _phoneBuilder;

        public NotificationConfigurationBuilder(IEmailConfigurationBuilder emailBuilder, IPhoneConfigurationBuilder phoneBuilder) 
        {
            _phoneBuilder = phoneBuilder;
            _emailBuilder = emailBuilder;
        }

        public INotificationConfiguration Build(ConfigType type, string commonObject, ConfigType emailConfigType, ConfigType phoneConfigType)
        {
            switch (type)
            {
                case ConfigType.Csv:
                    var config = new CsvNotificationConfiguration(_emailBuilder, _phoneBuilder);
                    config.Initialize(commonObject, emailConfigType, phoneConfigType);
                    return config;
                case ConfigType.Xml:
                    var cfg = new XmlNotificationConfiguration(_emailBuilder, _phoneBuilder);
                    cfg.Initialize(commonObject, emailConfigType, phoneConfigType);
                    return cfg;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }

    class InitModule : Ninject.Modules.NinjectModule
    {
        public override void Load()
        {
            Bind<IPhoneConfigurationBuilder>().To<PhoneConfigurationBuilder>();
            Bind<IEmailConfigurationBuilder>().To<EmailConfigurationBuilder>();
            Bind<INotificationConfigurationBuilder>().To<NotificationConfigurationBuilder>();
        }
    }
}

i)我简化了一些事情以保持简单。

ii)是的,我在工厂而不是ninject模块中进行了一些初始化,但是我需要弄清楚如何修复(我知道ninject有工厂扩展,希望对我有所帮助)。

iii)这个例子是最乐观的例子,其中Csv和Xml组件都需要相同的输入参数来初始化自己 - 这是我文章中提到的简单解决方案。

不幸的是,在我的现实生活中,Xml和Csv没有共同的参数对象,因此上面的两个解决方案,其中没有一个是我喜欢的。

同样,我可以简单地使用一些接口ICommonParameter commonObject而不是字符串commonObject,csv和xml都实现了这个公共参数,接口中暴露了一些字符串,然后可以在xml或csv中反序列化。感觉hackish,为什么我不会使用对象同一个东西,然后将其转换为XElement或CSVLine?与使用对象相比,此解决方案几乎没有优势。此解决方案和使用对象传递初始化参数都会导致运行时问题。

第二个解决方案,这意味着指数越来越多的泛型(基本上每个内部配置组件在其父级定义中都需要另一个泛型,然后我还需要添加泛型工厂)。

1 个答案:

答案 0 :(得分:0)

我肯定会将配置项的创建与其客户使用的界面分开,因此Initialize(T parameters)不应在IConfiguration之外的任何地方公开。

如果要将子配置分隔到自己的工厂中,则拥有多个IItemConfiguration实例,至少需要这些项的内部标识符。如果你能负担得起,只需尝试为整个配置设一个工厂。

namespace ConfigurationExperiment
{
    using System.Collections.Generic;
    using System.Xml.Linq;
    using System.Xml.XPath;

    public interface IConfiguration
    {
        IEnumerable<IItemConfiguration> ItemConfigurations { get; }
    }

    public interface IItemConfiguration
    {
        IDbConfiguration DbConfiguration { get; }
        INotificationConfiguration NotificationConfiguration { get; }
    }

    public interface IDbConfiguration
    {
        string ConnectionString { get; }
    }

    public interface INotificationConfiguration
    {
        IEmailConfiguration EmailConfiguration { get; }
        IPhoneConfiguration PhoneConfiguration { get; }
        ISmsConfiguration SmsConfiguration { get; }
    }

    public interface IEmailConfiguration
    {
        //primitive stuff here
    }

    public interface IPhoneConfiguration
    {
        //primitive stuff here
    }

    public interface ISmsConfiguration
    {
        //primitive stuff here
    }

    public interface IConfigurationRepository
    {
        IConfiguration GetConfiguration();
    }

    internal class ConfigurationRepository : IConfigurationRepository
    {
        private readonly XElement _configurationElement;

        public ConfigurationRepository(XElement configurationElement)
        {
            _configurationElement = configurationElement;
        }

        public IConfiguration GetConfiguration()
        {
            var result = new Configuration();
            foreach (var itemElement in _configurationElement.XPathSelectElements("//item"))
            {
                var item = new ItemConfiguration();
                //item.DbConfiguration = ...;
                //item.NotificationConfiguration = ...;
                result.Add(item);
            }
            return result;
        }

        private class Configuration : IConfiguration
        {
            private readonly List<ItemConfiguration> _itemConfigurations;

            public Configuration()
            {
                _itemConfigurations = new List<ItemConfiguration>();
            }

            public IEnumerable<IItemConfiguration> ItemConfigurations
            {
                get { return _itemConfigurations; }
            }

            public void Add(ItemConfiguration item)
            {
                _itemConfigurations.Add(item);
            }
        }

        private class ItemConfiguration : IItemConfiguration
        {
            public IDbConfiguration DbConfiguration { get; set; }
            public INotificationConfiguration NotificationConfiguration { get; set; }
        }
    }
}