我的配置类出现以下问题:
让我说我有以下几点:
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?与使用对象相比,此解决方案几乎没有优势。此解决方案和使用对象传递初始化参数都会导致运行时问题。
第二个解决方案,这意味着指数越来越多的泛型(基本上每个内部配置组件在其父级定义中都需要另一个泛型,然后我还需要添加泛型工厂)。
答案 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; }
}
}
}