Open/Closed Principle

The Setup

This week we are talking about the Open-Closed Principle. This principle states that a class (or method) should be open to extension and closed to modification. In other words we have to teach it new tricks without changing the actual code. “But that impossible” I hear you say. And I hope for all our sakes that you are wrong.

Let’s start with an example situation.

Imagine your client comes to you and asks you to implement an email sending functionality. You go and implement a class that sends emails by SMTP.

The next day the client says “All right, but Mondays are special and we need to send the email with flying pigeons”. You go put in place some conditional logic that says (in pseudo-code of course): if Monday then send_email_with_pigeons else send_email_with_smtp.

The next day the client says “That’s very good, but Tuesdays are even more special (or special-er) and we need to send mail with flying carpets”. You go and modify your conditional logic to something like: if Monday then send_email_with_pigeons else if Tuesday then send_email_with_flying_carpets else send_email_with_smtp.

And the client does this for every day of the week/month/year (because client), and you end up with 365 special cases for sending email.

What’s wrong with this picture?

Change, Change, Change

Change is what’s wrong with this picture. Not change itself, but changing something that already works, and has nothing to do with the latest requirement.

There is nothing wrong with coming back and changing already working code. After all we do this every time we refactor code, we change existing code. True, but we do it under certain conditions and respecting a set of strict rules. Changing the same code over and over, especially for adding new functionality (remember that when doing refactoring we don’t change/add functionality, only the implementation) is always an indicator of a problem.

public static class Program
{
    public static void Main()
    {
        var emailSender = new EmailSender();

        emailSender.SendEmail();
    }
}

public class EmailSender
{
    public void SendEmail()
    {
        var day = DateTime.Now.DayOfWeek;

        if (day == DayOfWeek.Monday)
        {
            // Send email with Flying Pigeons
            Console.WriteLine("Send email with Flying Pigeons");
        }
        else if (day == DayOfWeek.Tuesday)
        {
            // Send email with Flying Carpets
            Console.WriteLine("Send email with Flying Carpets");
        }
        else if (day == DayOfWeek.Wednesday)
        {
            // Send email with...
        }
        else if (day == DayOfWeek.Thursday)
        {
            // Send email with...
        }
        else if (day == DayOfWeek.Friday)
        {
            // Send email with...
        }
        else
        {
            // Send email with SMTP
            Console.WriteLine("Send email with SMTP");
        }
    }
}

In order to understand what the problem is with the code from the Open-Close Principle perspective, it might be useful to apply Single Responsibility Principle on it first.

So if we are to analyze it from a Single Responsibility Principle point of view, how many responsibilities does this code have?

Well, at least two, I would say. First and foremost it has to figure out the current day of the week, so it has the responsibility of making a choice.

Then it has to apply the corresponding email sending logic, meaning it must act upon that choice.

So the violation of Open-Closed Principle comes because the code mixes responsibilities, and because of these mixed responsibilities, when changing/updating the decision logic we might introduce bugs/unwanted changes into the email sending logic.

Obviously this is not always the case, the violation of the Open-Closed Principle does not stem necessarily from a violation of Single Responsibility Principle, but it just so happens in this example.

So what can we do? Well we could extract the email sending functionality in isolated classes which expose a common functionality. Meaning they implement an interface. The code will look something like this.

public class Program
{
    private static void Main(string[] args)
    {
        var emailSender = new EmailSender();

        emailSender.SendEmail();
    }
}

public interface ISendEmail
{
    void Send();
}

public class FlyingPigeonEmailSender : ISendEmail
{
    public void Send()
    {
        // Send email with Flying Pigeons
        Console.WriteLine("Send email with Flying Pigeons");
    }
}

public class FlyingCarpetEmailSender : ISendEmail
{
    public void Send()
    {
        // Send email with Flying Carpets
        Console.WriteLine("Send email with Flying Carpets");
    }
}

public class SmtpEmailSender : ISendEmail
{
    public void Send()
    {
        // Send email with SMTP
        Console.WriteLine("Send email with SMTP");
    }
}

public class EmailSender
{
    private readonly SmtpEmailSender smtpEmailSender = new();

    public void SendEmail()
    {
        var day = DateTime.Now.DayOfWeek;

        ISendEmail emailSender = day switch
        {
            DayOfWeek.Monday => new FlyingPigeonEmailSender(),
            DayOfWeek.Tuesday => new FlyingCarpetEmailSender(),
            // DayOfWeek.Wednesday => ...
            // DayOfWeek.Thursday => ...
            // DayOfWeek.Friday => ...
            // DayOfWeek.Saturday => ...
            // DayOfWeek.Sunday => ...
            _ => new SmtpEmailSender()
        };

        emailSender.Send();
    }
}

But we still have a situation. Our method is still doing two things.

Just so you can’t say later “Nobody told me”, remember that in programming 2 minus 1 is not always equal to 1.

Anyway, now the responsibilities mixed in our method are deciding what instance to work with based on the current day of the week, and then actually calling the functionality on that instance.

Like Ardalis used to say “New is Glue”, so now our method is glued to the knowledge of what instance to work with. Whenever a new way of sending the email is implemented, we have to modify our method to “know” and use the new functionality.

Luckily, there is an easy way to fix this. Instead of our method deciding what instance to use in order to send the email, we will extract this responsibility and inject the appropriate instance at runtime.

public class EmailSenderFactory
{
    public ISendEmail CreateInternalEmailSender(DayOfWeek day)
    {
        ISendEmail emailSender = day switch
        {
            DayOfWeek.Monday => new FlyingPigeonEmailSender(),
            DayOfWeek.Tuesday => new FlyingCarpetEmailSender(),
            // DayOfWeek.Wednesday => ...
            // DayOfWeek.Thursday => ...
            // DayOfWeek.Friday => ...
            // DayOfWeek.Saturday => ...
            // DayOfWeek.Sunday => ...
            _ => new SmtpEmailSender()
        };
        return emailSender;
    }
}

public class EmailSender
{
    private readonly CurrentDayProvider currentDayProvider;
    private readonly EmailSenderFactory emailSenderFactory;

    public EmailSender(CurrentDayProvider currentDayProvider, EmailSenderFactory emailSenderFactory)
    {
        this.currentDayProvider = currentDayProvider;
        this.emailSenderFactory = emailSenderFactory;
    }

    public void SendEmail()
    {
        var day = currentDayProvider.GetCurrentDay();

        var emailSender = emailSenderFactory.CreateInternalEmailSender(day);

        emailSender.Send();
    }
}

The implication is that now there is another place where the decision as to what instance to use lives.

We also have to extract the code that actually calls the email sending method on the instance, because this is yet another responsibility. We have to extract this functionality in order to respect the Single Responsibility Principle.

Now you could argue that we only moved someplace else the big, ugly switch-case statement, that still needs to change whenever a new email sending functionality is requested. But we actually did more than that.

We separated the concerns of making a decision and acting upon that decision. Now the only “reason to change” for that class is when the decision making process changes, and nothing else. And even that can be done away with, under certain conditions, as you can see in this article.

public class Program
{
    private static void Main()
    {
        var emailSender = new EmailSender(new(), new());

        emailSender.SendEmail();
    }
}

public interface ISendEmail
{
    void Send();
}

public class FlyingPigeonEmailSender : ISendEmail
{
    public void Send()
    {
        // Send email with Flying Pigeons
        Console.WriteLine("Send email with Flying Pigeons");
    }
}

public class FlyingCarpetEmailSender : ISendEmail
{
    public void Send()
    {
        // Send email with Flying Carpets
        Console.WriteLine("Send email with Flying Carpets");
    }
}

public class SmtpEmailSender : ISendEmail
{
    public void Send()
    {
        // Send email with SMTP
        Console.WriteLine("Send email with SMTP");
    }
}

public class CurrentDayProvider
{
    public DayOfWeek GetCurrentDay()
    {
        var day = DateTime.Now.DayOfWeek;
        return day;
    }
}

public class EmailSenderFactory
{
    public ISendEmail CreateInternalEmailSender(DayOfWeek day)
    {
        ISendEmail emailSender = day switch
        {
            DayOfWeek.Monday => new FlyingPigeonEmailSender(),
            DayOfWeek.Tuesday => new FlyingCarpetEmailSender(),
            // DayOfWeek.Wednesday => ...
            // DayOfWeek.Thursday => ...
            // DayOfWeek.Friday => ...
            // DayOfWeek.Saturday => ...
            // DayOfWeek.Sunday => ...
            _ => new SmtpEmailSender()
        };
        return emailSender;
    }
}

public class EmailSender
{
    private readonly CurrentDayProvider currentDayProvider;
    private readonly EmailSenderFactory emailSenderFactory;

    public EmailSender(CurrentDayProvider currentDayProvider, EmailSenderFactory emailSenderFactory)
    {
        this.currentDayProvider = currentDayProvider;
        this.emailSenderFactory = emailSenderFactory;
    }

    public void SendEmail()
    {
        var day = currentDayProvider.GetCurrentDay();

        var emailSender = emailSenderFactory.CreateInternalEmailSender(day);

        emailSender.Send();
    }
}

The CurrentDayProvider and EmailSenderFactory are customized for this particular scenario. But we could generalize the “pattern” for any pair of discriminator and factory that creates instances based on that discriminator. See this mini followup article.

Conclusion

We started off with what seemed like an innocent piece of code, and by applying the Open-Closed Principle we arrived at a nice loosely-coupled code. We had some help from the Single Responsibility Principle, because you will always have to apply more than one principle when coding, and this example offered us the opportunity for some nice synergies.