【发布时间】:2015-11-10 15:13:32
【问题描述】:
我的配置类有以下问题:
假设我有以下内容:
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) 有时,例如如果我想从 xml 获取电子邮件和电话,则在 INotificationConfiguration 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 没有共同的参数对象,因此上面的 2 个解决方案,其中没有一个是我喜欢的。
再一次,我可以简单地使用一些接口 ICommonParameter commonObject 而不是字符串 commonObject,csv 和 xml 都实现了这个公共参数,接口在其中公开了一些字符串,然后可以在 xml 或 csv 中反序列化。感觉 hackish,为什么我不将 object 用于同一件事,然后将其转换为 XElement 或 CSVLine ?与使用对象相比,这种解决方案几乎没有优势。而且这种解决方案和使用对象传递初始化参数都会导致运行时问题。
第二个解决方案意味着越来越多的泛型(基本上每个内部配置组件都需要在其父级定义中使用另一个泛型,然后我还需要添加泛型工厂)。
【问题讨论】:
-
您会在同一个应用程序中同时需要 XmlEmailConfiguration 和 CsvEmailConfiguration 吗?
-
@wigy 这是一个很好的问题,直到现在我还没有考虑过,但是不,现在我不需要同时使用 XmlEmailConfiguration 和 CsvEmailConfiguration。这也适用于所有接口。
-
认为最终结果是一个普通的旧数据类,而不是使用包含数据的对象也进行解析的所有这些接口?我觉得这会让你建立一个更简单的层次结构(接口更少)。一般来说,为 data 创建一个接口对我来说似乎很奇怪……我将接口用于功能而不是数据形状。
-
@31eee384 你好,最终的结果是主要使用ninject内部的这些接口,并有不同的ninject配置,以便我可以:a)随时切换组件类型(xml等)b ) 能够在任何时候轻松伪造任何配置。这只是我在尝试 IoC 和 DI,所以我可能在拥有过多接口方面是错误的。如果我按照你的方式进行,我会将解析的实现放在哪里?
-
@ImmoralWombat:关于将这个问题移至正确站点的标志,我不能 100% 确定 Code Review 会比 Stack Overflow 更好。见meta.stackexchange.com/questions/129598/…。
标签: c# generics interface decoupling