我正在使用优秀的Simple Injector Ioc框架,并希望"插入"多个电子邮件输出服务,即Mandrill,MailChimp等 我的问题是我正确地这样做,因为它导致了我的发送方法的演员。
所以我有一个简单的IEmailOutputService
public interface IEmailOutputService
{
string Identifier { get; }
bool Send(object message, object contents);
}
MandrillOutputService(缩短)
public class MandrillOutputService : IEmailOutputService
{
public MandrillOutputService()
{
//DI stuff here
}
public string Identifier => "Mandrill";
public bool Send(EmailMessage message, IEnumerable<TemplateContent> templateContents)
{
if (message == null)
return false;
//send code here
return true;
}
bool IEmailOutputService.Send(object message, object contents)
{
//TODO this doesnt look right!!
var m = message as EmailMessage;
var c = contents as IEnumerable<TemplateContent>;
//forwards method onto bespoke Mandrill Send method above
return Send(m, c);
}
}
我有EmailContext
获取登录用户的电子邮件输出提供程序,例如&#34; Mandrill&#34;继承了IEmailContext
public interface IEmailContext
{
string GetProvider();
}
EmailOutputComposite用于选择正确的电子邮件输出服务
public class EmailOutputComposite : IEmailOutputService
{
private readonly IEmailContext _emailContext;
private readonly IEnumerable<IEmailOutputService> _emailOutputServices;
public string Identifier => "EmailOutputComposite";
public EmailOutputComposite(
IEmailContext emailContext, IEnumerable<IEmailOutputService> emailOutputServices)
{
this._emailContext = emailContext;
this._emailOutputServices = emailOutputServices;
}
bool IEmailOutputService.Send(object message, object contents) =>
this._emailOutputServices
.FirstOrDefault(x => x.Identifier.ToLower() == this._emailContext.GetProvider())
.Send(message, contents);
}
最后在Simple Injector注册
container.RegisterCollection(typeof(IEmailOutputService), new[]
{
typeof(MandrillOutputService)
//other emailOutputServices to go here
});
container.Register(typeof(IEmailOutputService), typeof(EmailOutputComposite),
Lifestyle.Singleton);
所以我的问题是我正确地做到了这一点还是有更好的方法。我必须从数据库中获取用户电子邮件提供程序(Mandrill),因此无法想到另一种方法,但是我关注的是我必须在MandrillOutputService.Send方法中执行的操作。
答案 0 :(得分:3)
使用策略和工厂模式不是更简单,原谅我,我将稍微改变实现:
对于容器注册:
container.Register<EmailProviderFactory>(Lifestyle.Scoped);
container.Register<MandrillOutputService>(Lifestyle.Scoped);
container.Register<OtherOutputService>(Lifestyle.Scoped);
然后使用工厂解析我的电子邮件提供商:
public class EmailProviderFactory
{
private readonly Container container;
public EmailProviderFactory(Container container)
{
this.container = container;
}
public IEmailOutputService Create(string provider)
{
switch (provider)
{
case "Mandrill": // should be in a constants class
return container.GetInstance<MandrillOutputService>();
case "Other": // should be in a constants class
return container.GetInstance<OtherOutputService>();
default: throw new ArgumentOutOfRangeException("provider");
}
}
}
我已经将IEmailOutputService更改为具有显式类型的一个方法:
public interface IEmailOutputService
{
bool Send(EmailMessage message, IEnumerable<TemplateContent> contents);
}
电子邮件提供商:
public class MandrillOutputService : IEmailOutputService
{
public bool Send(EmailMessage message, IEnumerable<TemplateContent> templateContents)
{
// ...
}
}
public class OtherOutputService : IEmailOutputService
{
public bool Send(EmailMessage message, IEnumerable<TemplateContent> templateContents)
{
// ...
}
}
<强>用法:强>
foreach(var userEmailProvider in UserEmailProviders) {
// I'm assuming the factory is injected
var emailService = _emailProviderFactory.Create(userEmailProvider.Name);
emailService.Send(new EmailMessage(), new List<TemplateContent>());
}
我认为您不需要IEmailContext
或EmailOutputComposite
。通过使用EmailProviderFactory
,您只需在需要时创建特定的提供程序。
答案 1 :(得分:1)
我在设计中看到两个问题:
MandrillOutputService
抽象的可接受类型的子集,违反了IEmailOutputService
中的Liskov Substitution Principle;当用户提供对该特定实现无效的值时,这可能导致应用程序在运行时中断。Identifier
上的IEmailOutputService
属性违反Interface Segration Principle,因为这是消费者不使用的方法。实际上对此属性感兴趣的唯一类是EmailOutputComposite
。从抽象中删除Identifier
的优点是它可以简化单元测试,因为消费者可以调用的代码更少。它还简化了界面,这总是一件好事。我不确定如何修复LSP原则,因为我不清楚其他实现的外观。
关于ISP违规,您可以执行以下操作来修复它:
Identifier
的属性来标记实现。这允许您从界面中删除属性,但缺点是Composite只能在注入实际类型时过滤这些服务(并且不进行修饰,因为这样就不允许您检索这些属性)。switch
- case
语句。这再次允许您从界面中删除属性,但缺点是每次添加新实现时都必须更新组合(如果考虑Composition Root的复合部分,这可能不会那么糟糕)。Identifier
是字典的密钥。这消除了将Identifier
作为抽象的一部分的需要,但也从实现中删除了标识符(实际上可能是好的)。这是最后一个例子的一个例子:
container.RegisterSingleton<IEmailOutputService, EmailOutputComposite>();
container.RegisterSingleton(new Dictionary<string, Func<IEmailOutputService>>()
{
"Mandrill", CreateEmailServiceProducer<MandrillOutputService>(container),
"other", CreateEmailServiceProducer<Other>(container),
// ..
});
privte static Func<IEmailOutputService> CreateEmailServiceProducer<T>(Container c)
where T : IEmailOutputService =>
Lifestyle.Transient.CreateProducer<IEmailOutputService, T>(c).GetInstance;
复合材料的实施方式如下:
public class EmailOutputComposite : IEmailOutputService
{
private readonly IEmailContext _emailContext;
private readonly Dictionary<string, Func<IEmailOutputService>> _emailOutputServices;
public EmailOutputComposite(
IEmailContext emailContext,
Dictionary<string, Func<IEmailOutputService>> emailOutputServices)
{
_emailContext = emailContext;
_emailOutputServices = emailOutputServices;
}
public bool Send(object m, object c) => Service.Send(m, c);
IEmailOutputService Service => _emailOutputServices[_emailContext.GetProvider()]();
}
这是否真的取决于你。