I have a C# class like below in an app, and looking at ways to refactor it.
Send
method does not exist in the class. This is the solution that I came up with.emailType
and the customerId
. The consumer of the EmailService
knows only what type of email to be sent and the customerId
.class EmailService
{
Send(int emailType, int customerId)
{
switch(emailType)
{
case 1: SendSignupEmail(customerId);
break;
case 2: SendOrderEmail(customerId);
break;
case 3: SendCancellationEmail(customerId);
break;
}
}
SendSignupEmail(int customerId);
SendOrderEmail(int customerId);
SendCancellationEmail(int customerId);
}
You can replace the switch
with a Dictionary
and some configuration external to the class:
Define a new interface that represents the signature of the send methods:
interface ISendEmail
{
Send(int customerId);
}
For each send method, create a class that represents the send method.
class SendSignupEmail : ISendEmail
{
public Send(int customerId){}
}
class SendOrderEmail : ISendEmail
{
public Send(int customerId){}
}
class SendCancellationEmail : ISendEmail
{
public Send(int customerId){}
}
These are the email strategies.
Now EmailService
can become only a means by which emailType
s are routed to the correct implementation, and it need never change for new emailTypes (OCP).
public interface IEmailService
{
void Send(int emailType, int customerId);
}
class EmailService : IEmailService
{
private readonly Dictionary<int, SendEmail> senders = new Dictionary<int, SendEmail>();
public Send(int emailType, int customerId)
{
SendEmail email;
if (senders.TryGetValue(emailType, out email)) //replaces the switch
{ //found the email type, delegate the sending to the registered instance
email.Send(customerId);
}
else
{
//unregistered email type, this is like a default case in a switch
}
}
public Register(int emailType, SendEmail sender)
{
senders.Add(emailType, sender);
}
}
Then at one point in your system you can create this service and register the email implementations:
var emailService = new EmailService();
emailService.Register(1, new SendSignupEmail());
emailService.Register(2, new SendOrderEmail());
emailService.Register(3, new SendCancellationEmail());
IEmailService iEmailService = emailService;
You should reuse this implementation and pass the same instance to the clients (DIP) as an IEmailService
. Use of the interface here is ISP, because they do not require (and must not use) the classes Register
method.
So as you can see, a new email implementation will just be a new class and a new registration line, achieving OCP:
emailService.Register(4, new SendSomeNewEmail(serviceItDependsOn));
Notice serviceItDependsOn
, because I am using DIP I can inject extra services, or maybe an email template. Lots of additional complexity required by a new email can be handled without modifying either the client or EmailService
.
This differs from usual examples of strategy pattern because of the routing to the correct strategy, but it is still externalizing the work behind an interface and the strategy implementations are supplied to the class. I think those are the key components so I would still classify this as the strategy pattern.