Substitute Switch-Case With Data Structures

As discussed in the Open-Closed Principle article I suggested that in some certain situations, the decision on what email sender type to instantiate in the factory, can be represented in a data structure, instead of using switch-case (or if-else) structures.

I will paste the code here, because the changes are not major, and then I’ll explain the implications.

This is the main function that uses the factory class.

public class Program
{
    private static void Main()
    {
        var emailSenderFactory = new DataStructureDrivenEmailSenderFactory(new()
        {
            { "Monday", "FlyingPigeonEmailSender" },
            { "Tuesday", "FlyingCarpetEmailSender" }
        });

        var currentDayProvider = new CustomDayProvider(DayOfWeek.Monday);

        var emailSender = new EmailSender(currentDayProvider, emailSenderFactory);

        emailSender.SendEmail();
    }
}

This is the email sender factory, basically the start of the show.

public class DataStructureDrivenEmailSenderFactory
{
    private readonly Dictionary<string, string> senders;

    public DataStructureDrivenEmailSenderFactory(Dictionary<string, string> senders) => this.senders = senders;

    public ISendEmail CreateInternalEmailSender(DayOfWeek day)
    {
        var senderType = senders[day.ToString()];

        var objectHandle = Activator.CreateInstance(null!, senderType);

        var senderInstance = objectHandle?.Unwrap() as ISendEmail;

        return senderInstance ?? new SmtpEmailSender();
    }
}

The notable change is the appearance of the DataStructureDrivenEmailSenderFactory. This is the factory class that will be injected with the dictionary that describes the types of email senders and the “condition” when they will be instantiated.

How the factory actually instantiates the instance is nothing special. The thing that we need to observe and understand is that now all the “logic” that decides what email sender will be instantiated is contained in the data structure: the dictionary.

This approach only works as long as “the discriminant” that decides what instance is created is representable in a simple dictionary key. For instance this would not work if the decision is more complex, like some complicated boolean expression that cannot be represented in a dictionary key.

This is the special circumstance I was referring to: the fact that the condition can be represented inside a dictionary key.

It’s a pretty useful thing to have in your tool belt. It’s not the default implementation you have to think at, but take it into consideration, because it has it’s advantages and can be quite useful.

Of course, it also comes with some disadvantages, namely that all you can work with are strings. And strings are very prone to typos and the code needed to handle them can be quite unwieldy and error prone as well.