Question

I found this code example explaining Open / Closed principle.

Code before application of 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
}

And refactored code:

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
    }
}

Can you explain me the reason to still keep Logger class with private IMessageLogger instance? I would simply avoid it by:

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

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

public class PrinterLogger : ILogger
{
    public void Log(string message)
    {
        // Code to send message to printer
    }
}

The only reason I can think about is, that in suggested solution with Logger class, we could still refer to this class in client code, but we still need to modify all Log(msg) calls to remove LogType arguments.

Was it helpful?

Solution

As long as the new Logger class does not contain more code, the example seems to be contrived. But imagine that class gets some more methods, with code which is independent from the concrete IMessageLogger, for example:

public class Logger
{
    // ...
    public void LogFormatted(string formatString, string[] parameters )
    {
        string message = string.Format(formatString, parameters);
        _messageLogger.Log(message);
    }
}

Then it makes sense to have a class where you can implement this additional code in one place, keeping the program DRY.

The pattern we see here is the classic "strategy" pattern. This becomes obvious when you rename IMessageLogger to ILoggingStrategy, and the derived classes to ConsoleLoggingStrategy and PrinterLoggingStrategy.

It is also a demonstration of the OCP, because now you can put Logger and IMessageLogger (or ILoggingStrategy) into a reusable library (or framework), whilst new IMessageLogger derivations can reside in application code using that lib. So the lib, including the Loggerclass, won't have to be changed if someone wants to add something like a new strategy like CloudLoggingStrategy, for example. In the original code, however, a lib containing the Logger class would have to be changed for such an extension.

Licensed under: CC-BY-SA with attribution
scroll top