mht*_*sbt 2 c# architecture dependency-injection
最近,我遇到了一个必须基于参数选择类型的问题。例如:用于发送通知的类,该类应根据输入参数选择正确的通道(电子邮件,短信,...)。
我看起来像这样:
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
这里的问题是,当我使用这种构造时,构造函数在发送通知的所有不同方法中迅速增长。
我真的不喜欢这样,这使得对选择单元进行单元测试变得难以操作。
我不能简单地说,new email();因为通知类型的电子邮件将依赖IEmailManager,这只会解决问题。
是否有某种模式可以执行相同的操作,但是方式更好,更清洁?
我建议您将IEmail和ISms接口组合成一个IMessageService(前提是不违反Liskov Substitution Principal),并使用策略模式使您的通知服务能够选择所使用的类型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");
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
在您的DI容器中,注册所有IMessageService要解析为数组的匹配类型。例如,在StructureMap中:
container.For<IMessageService>().Use<EmailMessageService>();
container.For<IMessageService>().Use<SmsService>();
Run Code Online (Sandbox Code Playgroud)
或者,您也可以使用“扫描”自动提取事实之后添加的新类型。
var container = new Container(x => x.Scan(scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.AddAllTypesOf<IMessageService>();
}));
Run Code Online (Sandbox Code Playgroud)
无论哪种方式,都需要用容器注册类型才能满足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");
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,如果采用这种方法,EmailStrategy则以后决定添加或删除IMessageService-时,无需更改类-您只需要更改DI配置即可。