BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Programming Concepts

Open / Closed Principle

The third article in the SOLID Principles series describes the Open / Closed Principle (OCP). The OCP states that all classes and similar units of source code should be open for extension but closed for modification.

The Principle

The Open / Closed Principle (OCP) states that classes should be open for extension but closed for modification. "Open to extension" means that you should design your classes so that new functionality can be added as new requirements are generated. "Closed for modification" means that once you have developed a class you should never modify it, except to correct bugs.

The two parts of the principle appear to be contradictory. However, if you correctly structure your classes and their dependencies you can add functionality without editing existing source code. Generally you achieve this by referring to abstractions for dependencies, such as interfaces or abstract classes, rather than using concrete classes. Such interfaces can be fixed once developed so the classes that depend upon them can rely upon unchanging abstractions. Functionality can be added by creating new classes that implement the interfaces.

Applying the OCP to your projects limits the need to change source code once it has been written, tested and debugged. This reduces the risk of introducing new bugs to existing code, leading to more robust software. Another side effect of the use of interfaces for dependencies is reduced coupling and increased flexibility.

Example Code

To demonstrate the application of the OCP, we can consider some C# code that violates it and explain how the classes can be refactored to comply with the principle:

public class Logger
{
    public void Log(string message, LogType logType)
    {
        switch (logType)
        {
            case LogType.Console:
                Console.WriteLine(message);
                break;

            case LogType.File:
                // Code to send message to printer
                break;
        }
    }
}


public enum LogType
{
    Console,
    File
}

The above sample code is a basic module for logging messages. The Logger class has a single method that accepts a message to be logged and the type of logging to perform. The switch statement changes the action according to whether the program is outputting messages to the console or to the default printer.

If you wished to add a third type of logging, perhaps sending the logged messages to a message queue or storing them in a database, you could not do so without modifying the existing code. Firstly, you would need to add new LogType constants for the new methods of logging messages. Secondly you would need to extend the switch statement to check for the new LogTypes and output or store messages accordingly. This violates the OCP.

Refactored Code

We can easily refactor the logging code to achieve compliance with the OCP. Firstly we need to remove the LogType enumeration, as this restricts the types of logging that can be included. Instead of passing the type to the Logger, we will create a new class for each type of message logger that we require. In the final code we will have two such classes, named "ConsoleLogger" and "PrinterLogger". Additional logging types could be added later without changing any existing code.

The Logger class still performs all logging but using one of the message logger classes described above to output a message. In order that the classes are not tightly coupled, each message logger type implements the IMessageLogger interface. The Logger class is never aware of the type of logging being used as its dependency is provided as an IMessageLogger instance using constructor injection.

The refactored code is as follows:

public class Logger
{
    IMessageLogger _messageLogger;

    public Logger(IMessageLogger messageLogger)
    {
        _messageLogger = messageLogger;
    }

    public void Log(string message)
    {
        _messageLogger.Log(message);
    }
}


public interface IMessageLogger
{
    void Log(string message);
}


public class ConsoleLogger : IMessageLogger
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}


public class PrinterLogger : IMessageLogger
{
    public void Log(string message)
    {
        // Code to send message to printer
    }
}
8 January 2011