最近我遇到了一个问题,我必须根据参数选择一个类型。例如:用于发送应根据输入参数选择正确频道(电子邮件,短信,...)的通知的类。
我看起来像这样:
public class NotificationManager
{
IEmail _email;
ISms _sms;
public NotificationManager (IEmail email, ISMS sms)
{
_email = email;
_sms = sms;
}
public void Send(string type)
{
switch(type)
{
case "email":
_email.send;
break;
case "sms":
_sms.send;
break;
}
}
}
这里的问题是,当我使用这种构造时,构造函数会随着发送通知的所有不同方法快速增长。
我真的不喜欢这个,它使单位测试这个选择单元无法管理。
我不能简单地说new email();
,因为通知类型的电子邮件将依赖于IEmailManager,这只会解决问题。
是否有某种模式可以做同样的事情,但是以更好,更清洁的方式?
答案 0 :(得分:4)
我建议您将IEmail
和ISms
接口合并到IMessageService
(假设不违反Liskov Substitution Principal)并使用{ {3}}使您的通知服务能够选择所使用的IMessageService
的类型(或多种类型)。
public interface IMessageService
{
void Send(string subject, string body);
bool AppliesTo(IEnumerable<string> providers);
}
public class EmailMessageService : IMessageService
{
public EmailMessageService(/* inject dependencies (and configuration) here */)
{
// Set dependencies to private (class level) variables
}
public void Send(string subject, string body)
{
// Implementation - use dependencies as appropriate
}
public bool AppliesTo(IEnumerable<string> providers)
{
return providers.Contains("email");
}
}
public class SmsMessageService : IMessageService
{
public SmsMessageService(/* inject dependencies (and configuration) here */)
{
// Set dependencies to private (class level) variables
}
public void Send(string subject, string body)
{
// Implementation - use dependencies as appropriate
}
public bool AppliesTo(IEnumerable<string> providers)
{
return providers.Contains("sms");
}
}
public interface IMessageStrategy
{
void Send(string message, string body, string provider);
void Send(string message, string body, IEnumerable<string> providers);
}
public class MessageStrategy : IMessageStrategy
{
private readonly IMessageService[] messageServices;
public MessageStrategy(IMessageService[] messageServices)
{
if (messageServices == null)
throw new ArgumentNullException("messageServices");
this.messageServices = messageServices;
}
public void Send(string message, string body, string provider)
{
string[] providers = provider.Split(';').Select(p => p.ToLower().Trim()).ToArray();
this.Send(message, body, providers);
}
public void Send(string message, string body, IEnumerable<string> providers)
{
foreach (IMessageService messageService in messageServices)
{
if (messageService.AppliesTo(providers))
{
messageService.Send(message, body);
}
}
}
}
在您的DI容器中,注册所有匹配IMessageService
的类型以作为数组进行解析。例如,在StructureMap中:
container.For<IMessageService>().Use<EmailMessageService>();
container.For<IMessageService>().Use<SmsService>();
或者您可以使用“扫描”自动拾取新类型,这些类型会在事后添加。
var container = new Container(x => x.Scan(scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.AddAllTypesOf<IMessageService>();
}));
无论哪种方式,只需使用容器注册类型即可满足IMessageService[]
依赖关系。
然后,只需将IMessageStrategy
注入需要消息传递的类并传递魔术字符串以选择要使用的消息服务类型。
public class SomeService : ISomeService
{
private readonly IMessageStrategy messageStrategy;
public SomeService(IMessageStrategy messageStrategy)
{
if (messageStrategy == null)
throw new ArgumentNullException("messageStrategy");
this.messageStrategy = messageStrategy;
}
public void DoSomething()
{
// Send a message via email
this.messageStrategy.Send("This is a test", "Hello", "email");
// Send a message via SMS
this.messageStrategy.Send("This is a test", "Hello", "sms");
// Send a message via email and SMS
this.messageStrategy.Send("This is a test", "Hello", "email;sms");
}
}
请注意,如果采用这种方法,如果您稍后决定添加或删除EmailStrategy
,则IMessageService
课程不需要更改 - 您只需更改DI配置即可。