2

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.

  • This feels to me like an example that was oversimplified just a bit too much, and now doesn't make any sense in practice (i.e., yes you're right). The Logger(IMessageLogger) form would make sense iff the Logger class contained some meaningful logic of its own other than blind passthroughs to the IMessageLogger. For instance, a Logger that prepends timestamps to every message or tries to prevent logging the same message too many times might be useful. But I've always been a bit fuzzy on how the O/C principle is supposed to work... – Ixrec Feb 5 '16 at 9:51
  • Ok this does make sense. Maybe something like // Some additional logic comment inside Logger class would make it more clear. – Majak Feb 5 '16 at 9:54
  • 1
    This isn't an example of the Open / Closed principle. It's an exercise in converting a switch statement into one based on subtype polymorphism. – David Arno Feb 5 '16 at 10:34
  • In the "before refactoring" the client code hints at the kind of logging it wants (via the LogType parameter). In "after refactoring" this is reversed in that the the client is given a specific kind of logging. I think that this reversal makes for an odd example. – David Soroko Feb 5 '16 at 11:31
  • 1
    @DavidArno: it is both, an OCP example and what you wrote. – Doc Brown Feb 5 '16 at 12:15
5

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.

  • 1
    Great complex answer! I really like your explanation of OCP together with "strategy" pattern here. – Majak Feb 5 '16 at 12:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.