依赖注入类型选择 [英] Dependency injection type-selection
问题描述
最近,我遇到了一个必须基于参数选择类型的问题.例如:用于发送通知的类,该类应根据输入参数选择正确的渠道(电子邮件,短信,...).
Recently I've come accross a problem where I have to select a type based on a parameter. For example: a class used for sending notifications that should select the right channel (email, sms, ...) based on an input-parameter.
我看起来像这样:
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;
}
}
}
这里的问题是,当我使用这种构造时,构造函数迅速增长,并且具有各种发送通知的方法.
The problem here is that when I use this kind of construction, the constructor fastly growns very large with all the different methods of sending notifications.
我真的不喜欢这样,这使得对这个选择单元进行单元测试变得难以操作.
I really don't like this, and it makes unit-testing this selection-unit unmanagble.
我不能简单地说new email();
,因为通知类型的电子邮件将依赖IEmailManager,这只会解决问题.
I cannot simply say new email();
because the notification-type email will rely on IEmailManager, and this will only move the problem.
是否存在某种可以做的相同的模式,但是以更好,更清洁的方式进行?
Is there some kind of pattern that will do the same, but in a better, cleaner way?
推荐答案
我建议您将IEmail
和ISms
接口合并为IMessageService
(前提是不违反Liskov替代负责人),并使用
I would suggest that you combine your IEmail
and ISms
interfaces into an IMessageService
(provided that doesn't violate the Liskov Substitution Principal) and to use a strategy pattern to enable your notification service to be able to select the type (or types) of IMessageService
that are used.
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中:
Usage
In your DI container, register all types that match IMessageService
to be resolved as an array. For example, in StructureMap:
container.For<IMessageService>().Use<EmailMessageService>();
container.For<IMessageService>().Use<SmsService>();
或者,您也可以使用扫描"自动提取事实之后添加的新类型.
Or alternatively you can use Scan to pickup new types automatically that are added after the fact.
var container = new Container(x => x.Scan(scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.AddAllTypesOf<IMessageService>();
}));
无论哪种方式,只要满足IMessageService[]
依赖性,都需要在容器中注册类型.
Either way, registering the types with the container is all you need to satisfy the IMessageService[]
dependency.
然后只需将IMessageStrategy
注入需要消息传递的类中,并传递魔术字符串以选择要使用的消息服务类型即可.
Then it is just a matter of injecting IMessageStrategy
into a class that requires messaging and passing the magic string to select which types of message services to use.
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
类-您只需更改DI配置.
Note that if you take this approach, your EmailStrategy
class won't need to change if you decide later to add or remove a IMessageService
- you only need to change the DI configuration.
这篇关于依赖注入类型选择的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!