依赖注入类型选择
Dependency injection type-selection
最近我遇到了一个问题,我必须 select 基于参数的类型。例如:class 用于发送通知,应该 select 基于输入参数的正确渠道(电子邮件、短信等)。
我看起来像这样:
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;
}
}
}
这里的问题是,当我使用这种构造时,构造函数会随着发送通知的所有不同方法而迅速变得非常大。
我真的不喜欢这个,它使这个 selection-unit 的单元测试变得难以管理。
我不能简单的说new email();
因为通知类邮件会依赖IEmailManager,这样只会把问题转移。
是否有某种模式可以以更好、更简洁的方式执行相同的操作?
我建议您将 IEmail
和 ISms
接口组合成一个 IMessageService
(前提是不违反 Liskov Substitution Principal) and to use a strategy pattern 以启用您的通知服务能够 select 使用的 IMessageService
类型。
重构为 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
注入需要消息传递的 class 并将魔术字符串传递给 select 要使用的消息服务类型即可。
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");
}
}
请注意,如果您采用这种方法,如果您稍后决定添加或删除 IMessageService
,您的 EmailStrategy
class 将不需要更改 - 您只需要更改DI 配置。
最近我遇到了一个问题,我必须 select 基于参数的类型。例如:class 用于发送通知,应该 select 基于输入参数的正确渠道(电子邮件、短信等)。
我看起来像这样:
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;
}
}
}
这里的问题是,当我使用这种构造时,构造函数会随着发送通知的所有不同方法而迅速变得非常大。
我真的不喜欢这个,它使这个 selection-unit 的单元测试变得难以管理。
我不能简单的说new email();
因为通知类邮件会依赖IEmailManager,这样只会把问题转移。
是否有某种模式可以以更好、更简洁的方式执行相同的操作?
我建议您将 IEmail
和 ISms
接口组合成一个 IMessageService
(前提是不违反 Liskov Substitution Principal) and to use a strategy pattern 以启用您的通知服务能够 select 使用的 IMessageService
类型。
重构为 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
注入需要消息传递的 class 并将魔术字符串传递给 select 要使用的消息服务类型即可。
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");
}
}
请注意,如果您采用这种方法,如果您稍后决定添加或删除 IMessageService
,您的 EmailStrategy
class 将不需要更改 - 您只需要更改DI 配置。