The Open/Closed Principle – OCP

This entry is part 4 of 4 in the series SOLID

The second principle we’re going to talk about is the Open/Closed Principle. If we look at Wikipedia, we will see the following definition:

The Open/Closed Principle states that software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

Problem Statement

What it means, in essence, is that when we need to introduce a change, we shouldn’t dig deep into the system and change its behavior by changing dozens of classes. We should be able to introduce a change by adding new code, not by changing the existing. Such software which allows changes via introducing new classes rather than via changing the existing source code is open for extension and closed for modification.

How to change behavior without changing the code base? The answer is simple – dynamic dispatch or polymorphism. We can achieve a very supple design by harnessing the power of Object-Oriented programming. In practice, it means that we need to rely on abstractions, rather than on concrete or direct implementations. In C#, we will rely on interfaces and abstract classes.

Why OCP?

Ok, but what’s the problem with changing the existing code? Why do we need to adhere to the Open/Closed Principle at all?

  • The first reason is that there is a high chance of introducing bugs during the modification process. It’s much easier to introduce a bug modifying something that is complex and already exists than to introduce a bug adding new code.
  • The second reason is relevant, and it is almost always relevant when you’re trying to modify the behavior of an API which is already in use by many clients.

The first danger here is to change the behavior expected by clients. Imagine that there are one thousand clients of a method which returns 100 in the case of failure and that clients have some compensation logic for that case. Imagine then that you modified the behavior of the method upon which those one thousand clients depend. The consequences are dramatic. I would pray for that developer who decided to introduce such a change, which breaks the code of one thousand clients.

The second danger is to modify the API’s signatures. Such modifications immediately break all the dependent clients. Believe me; clients will not thank you for such modifications. By the way, breaking changes may also cause ripple effects because there can exist hundreds of clients which depend on other clients which depend on your code.

Modifying the behavior by adding new code is actually conforms to what customers think when they ask for new features. When customers ask for a new feature they think that features will be added, they don’t think that developers will modify anything.

The idea might seem ridiculous from the first sight. Indeed, is that possible to write code that will never be modified again? Of course, this is not possible in practice. Well, theoretically it’s possible to write all the code in this way, but it is not practical. In programming, we often set some ideal goals which can’t be ever achieved. For example, we strive to write correct code, and theoretically, we can write code the correctness of which can be statically verified. Unfortunately, in 99% of cases, such development process is so expensive that no one is capable of investing so much money.

Another point which is obvious but I need to mention it is that we absolutely must modify the existing code if it contains a bug. So, bug fixing is OK from the Open/Closed principle point of view.

Mayer’s Definition of OCP

Let’s take a step back and look back into 1988. 1988 is the year when Bertrand Meyer described OCP for the first time. Meyer treated OCP a little bit differently than Uncle Bob. OCP from Meyer’s point of view was essentially an equivalent to the Protected Variation pattern. The Protected Variation pattern means the following: Identify points of predicted variation and create a stable interface around them. Talking here about an interface, we don’t mean the C# interface construct. An interface implies any API. A class has its own interface which is represented by its public API. We can notice here a subtle difference between Meyer’s and Martin’s definitions. Meyer’s definition is more about backward compatibility at the API level. So, if changes don’t break the backward compatibility, then OCP is met.
When exactly may we need to change the behavior without changing the interface? For example, when a new client appears which requires very similar but a little bit different behavior.

Single Choice Principle

The last point I want to address is the Single Choice Principle closely related to Open/Closed principle. I prefer to name this principle the “Single Source Principle” and you’ll understand why shortly. Let’s imagine the case when we need to decide which implementer to create depending on an argument. For solving such problems, we often rely on the Factory pattern.

Here is an example:

public class BankTerminalFactory
{
    public static IBankTerminal CreateBankTerminal(BankTerminalModel model)
    {
        switch (model)
        {
            case BankTerminalModel.Brp:
                return new BrpTerminal();
            case BankTerminalModel.Dcp:
                return new DcpTerminal();
            default:
                throw new ArgumentException("Unknown model");
        }
    }
}

As you see here, we have a factory method which takes a parameter of BankTerminalModel enumeration. Depending on that parameter, the factory method creates a corresponding implementation of the IBankTerminal interface.
Does this factory method violate the Open/Closed principle? Of course, it does.

If we roll out a new implementation of the IBankTerminal interface, we will modify this code by adding a new case statement.

How can we solve the problem adhering the Open/Closed principle? We can create a factory of the BankTerminalFactory. But who will create that factory of this factory? Somewhere, in the end, we need to select the appropriate implementation and create the instance. We can partly solve the problem by introducing an IoC-Container, but this will just move the problem, it doesn’t solve the problem entirely.

If you’re not familiar with the notion of Dependency Injection and IoC-containers, as a side note, I’d recommend you a book which is an absolute bestseller written by Mark Seemann “Dependency Injection in .NET”.

So, the Principle of Single Choice sounds like this:

“Whenever a software system must support a set of alternatives, one and only one module in the system should know their exhaustive list.”

Yes, we’re violating the OCP by having such an implementation of the factory method, but there is no meaning to try to push the OCP further by any means. We should understand that in such cases all we need is to isolate such a responsibility in a single module and only that module should be changed in case of introducing new implementations.

Classic Violation

Here is an example of a classic OCP violation:

public string FindDevice(DeviceModel model)
{
    SerialPort port = new SerialPort();
    switch (model)
    {
        case DeviceModel.BillAccepterCashCode:
            {
                port.BaudRate = 9600;
                port.Parity = Parity.Even;
                port.Handshake = Handshake.RequestToSend;
                return Find(port);
            }
        case DeviceModel.BillDispenserEcdm:
            {
                port.BaudRate = 4800;
                port.Parity = Parity.Mark;
                port.Handshake = Handshake.RequestToSendXOnXOff;
                return Find(port);                        
            }              
        case DeviceModel.CoinAccepterNri:
            {
                port.BaudRate = 19200;
                port.Parity = Parity.Odd;
                port.Handshake = Handshake.XOnXOff;
                return Find(port);                        
            }
        case DeviceModel.CoinDispenserCube4:
            {
                port.BaudRate = 9600;
                port.Parity = Parity.Space;
                port.Handshake = Handshake.None;
                return Find(port);                        
            }
        case DeviceModel.CoinDispsenerSch2:
            {
                port.BaudRate = 4800;
                port.Parity = Parity.Even;
                port.Handshake = Handshake.None;
                return Find(port);                        
            }
        default:
            throw new ArgumentException($"Unknown model: {model}.");
    }            
}

We have a switch statement here to run the searching for a device method. In fact, if there is only one switch statement used with the DeviceModel enumeration, then the OCP is not violated. If switch statements used with that enumeration start to spread all over the code base then we have a violation of the OCP. That would be a violation since you couldn’t easily extend that enumeration. Adding a value to that enumeration would force you to change all the corresponding switch statements in the code base (handling new value). This example demonstrates the OCP violation from the Martin’s point of view. We don’t have any problems with this code from the Mayer’s point of view. At least yet.

Classic Fix

We firstly need to abstract away the Find method by extracting the IDevice interface.

public interface IDevice
{
    string Find();
}   

Now we can implement it in different device models. Here is just one example:

public class CoinDispenserCube4 : IDevice
{
    public CoinDispenserCube4()
    {
        Port = new SerialPort
        {
            BaudRate = 9600,
            Parity = Parity.Space,
            Handshake = Handshake.None
        };
    }

    public SerialPort Port { get; }

    public string Find()
    {            
        foreach (string portName in SerialPort.GetPortNames())
        {
            //test if device is can be connected
            Port.Write("special code");
            if (Port.ReadByte() == 0)
                return portName;
        }
        return null;
    }
}

Each device has to implement this interface. So, the internal details of searching for a concrete device are encapsulated in corresponding device classes.

The last piece I want to implement is an orchestrator class which accepts the IDevice interface and delegates the searching process to the IDevice implementer:

public class DeviceFinder
{
    private readonly IDevice _device;

    public DeviceFinder(IDevice device)
    {
        _device = device;
    }

    public string Find()
    {
        return _device.Find();
    }
}

If you have some common logic between devices, it might be beneficial to use an abstract class instead of an interface. All in all, we ended up with a design which allows us to roll out a new Device implementation without changing the code base, we just should add a new class which represents a device. So, our code is closed for modification and open for extension.
In both cases, we end up with multiple classes which inherit either from an abstract class or an interface.

Consequences

Such approach provides simpler extension if a new type is required, but it makes harder to add new operation since you’ll have to implement them in each class. As I’ve already said, there is always some kind of tradeoff involved. Welcome to engineering!

In this case, we harness the power of inheritance and composition. Client code, in this case, depends on abstraction. Client code should pass a plug into a model. The model itself doesn’t rely on inheritance; it relies on composition, it just stores the IDevice interface delegating to it appropriate responsibilities.
There is one little thing I want to mention before moving further. Do you think that applying that refactoring we took into account all the possible directions in which the system can grow? Not a chance!

That is a fundamental problem of predicting the future. Unfortunately, we can only assume what will happen in the future. That’s why customers always can find features which don’t fit into your design. Implementing them, you’ll have to redesign and rewrite parts of the system which already exist.
Does this fact make OCP and other principles unimportant? Does it mean that we can forget about any principles, in case we can’t predict the future anyway?

And here is the answer. Yes, unfortunately, we can’t achieve a super-supple design which allows to introduce any possible features without problems. We can only struggle to overcome the problem of unpredictable future. Developers came up with complex development methodologies the intent of which is to allow us to not bother so much about the problem of unpredictable future.

The first one is an old waterfall process, or the so-called “big design upfront”. This methodology suggests struggling with unpredictable future by trying to consider all the details of the project, creating all the possible abstractions, introducing extension points in all those places where a system can potentially be changed. The worldwide practice shows that in most cases adhering to the principles of this methodology leads to overengineering and thus to expenses which grow very fast.

The second methodology is called “Agile”. There are many agile methodologies nowadays, and we are not going to discuss them. I’ll only give a short description of what “Agile” is in essence. Agile methodologies are aimed at setting relationships between the development team and a customer. From the first day of a project, development team starts to work on a customer rolling out software by small increments. So, the Agile methodology suggests the iterative process of development with high involvement of a customer into the development process. In conjunction with SOLID principles and other best practices it allows to anticipate possible changes and react to them as quickly as possible by performing the refactoring. Of course, Agile doesn’t imply that a development team doesn’t design anything upfront. Agile is somewhere between two extreme points of no design up front and big design up front.

Of course, there are no techniques which can for sure protect you from all the possible changes. But the Agile process is how we’re trying to live with all that injustice in the modern days.

Common Smells

The most common smell of OCP violation is the appearance of many conditional branches either with if-else or with switch/case statements.

You have three common approaches to adhere to the OCP.

  • Building a handling sequences with delegates which are naturally the implementations of the chain of responsibility pattern. Look for the Chain of Responsibility pattern. This pattern is amost built-in with events into the .NET platform. So, we use it when we need to pass a message and handle it by several handlers. The chain of responsibility provides the way to implement that in a loosely coupled manner. Otherwise you can end up with coupled objects.
  • Another way is to rely on a classic inheritance with a template method pattern or to implement a Visitor pattern if you need to provide the ability for a client to define operations.
  • The last way is to rely on a composition rather than on inheritance. Most likely you would rely on the Strategy pattern to implement a composition. Remember, that composition is generally more preferred than classic inheritance.

Conclusion

The open/closed principle is all about changes. Following OCP, you can achieve a very supple design which is ready for introducing new features without pain. To achieve the supple design, it should be open for extension and closed for modification. So, the most frequently changing parts of the system have to be isolated.

One very important thing is that we need to isolate a responsibility for creating objects in a single module and only that module should be changed in case of introducing new implementations. This principle is called the “Single Choice” principle.

You’ve also learned that we don’t have at our disposal any magic tools which allow us to protect ourselves from all the possible changes completely. And it doesn’t mean that we should start to create tons of extensions points anticipating changes in the future all over the code base. To overcome the problem, we’re trying to use the so-called “agile design.”


You can take the full video course “Software Architecture: Meta and SOLID Principles” with a huge discount clicking here.

Also, consider the option to become a patron on Patreon, thank you in advance!

DateTime Fundamentals in .NET and SQL Server

My new video course “Date and Time Fundamentals in .NET and SQL Server” is released!
Take it right now just for 10$!

DateTime

Build a solid foundation of working with Date and Time with this course

Almost all the software works with date and time in one form or another. Some programs, of course, need to work with date and time more than other. However, date and time hide many pitfalls even in very simple scenarios. We all know that security and logging are regular aspects of almost any program. There is a wise phrase that sounds like: “Developers recall about security when it is too late.” The same can be said about date and time. Most developers don’t even imagine how many bugs are hiding behind the simple face of date and time. Those bugs are sitting in programs and waiting for their moment to blow up your production code causing very unfortunate repercussions. That’s why working with date and time is the full-fledged and very important aspect of software development.
Have you watched magnificent enlightening TV Shows by BBC? This course in some parts is like those BBC shows, because you’ll learn many interesting surprising facts. Besides, being familiar with peculiarities of working with date and time, you can write much more reliable and maintainable applications.

Continue reading

Single Responsibility Principle – SRP

This entry is part 3 of 4 in the series SOLID

SRP Definition

In this article, we will look at what is single responsibility principle and look at some simple examples in C# which demonstrate the single responsibility principle and how to adhere to the SRP.
Single Responsibility Principles or SRP in short states that every object should have a single responsibility, and that responsibility should be entirely encapsulated by the class. This is the definition taken from Wikipedia. It’s very hard to understand what does it mean. For example, the first two questions which come to my mind are:

  • how to define those responsibilities?
  • how to calculate the number of responsibilities of a certain class?

Continue reading

Software Architecture: Meta and SOLID Principles in C#

This entry is part 1 of 4 in the series SOLID

Learn how to develop maintainable software systems applying Meta and SOLID Principles.

Teaching Approach

No fluff, no ranting, no beating the air. I esteem your time. The course material is succinct, yet comprehensive. All important concepts are covered. Particularly important topics are covered in-depth. For absolute beginners, I offer my help on Skype absolutely free, if requested.

Take this course, and you will be satisfied!

SOLID is an acronym which stands for SRP, OCP, LSP, ISP and DIP. These five acronyms in their turn stand for:

  • Single Responsibility Principle
  • Open/Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

In this course, you’ll learn how to apply meta and SOLID principles so that your application will live a long healthy life. It means you are going to learn how to write code of the high quality: readable, understandable and reliable.

Continue reading

What’s New in C# 6, C# 7 and Visual Studio 2017

C# is one of the most powerful languages in the modern world of programming. It has one of the most powerful type systems. There was a battle for some time between Java and C# and now we can say for sure that C# won that battle from the language features point of view.

C# 6 is already released and fully available with Visual Studio 2015. C# 7 is under development still, but many announced features are already implemented and we can play with them in Visual Studio 2017 which is currently (when I’m writing this) itself under development (release candidate is available).

Continue reading

Modern .NET Ecosystem and .NET Core

Last years a great number of technologies came up to the world of .NET platform: nowadays we have the full (classic) .NET framework with CLR as a runtime, Mono with its own runtime, .NET Core with Core CLR, WinRT, UWP and Xamarin, a new JIT compiler RyuJit, .NET Standard, PCL, .Net Native, new Roslyn compiler with open API, NuGet based project management. God’s sake! It’s so simple to get lost in that ocean of technologies. You need to understand the overall picture to feel comfortable today. Didn’t you feel like a small fish in the ocean last time? Well, I did. I questioned myself, “what the hell is going on around me?” I didn’t quite understand the directions in which technologies develop. I didn’t know what to expect, what to learn next. In such situation, you feel helpless. And what we are going to do in the course is that we’re going to eliminate this nasty feeling of being helpless by learning all the most important notions in the modern .NET platform.

Continue reading

Handling Errors and Exceptions in C#. Part 3

This is the third and most comprehensive part about handling errors and exceptions in C#. Here are the first and the second part. Also, you can take a look at the blog post about global exceptions handling in WPF applications.

Errors Handling and many other topics you’ll find in my new video course “API in C#: The Best Practices of Design and Implementation”. Take it with 60% discount! Also, nothing can stop you from reading further)))

The problem of errors handling is really an old one. Despite that, I feel a lack of sources which aggregate the information and reveal all the problems relate to exceptions.
The first question which immediately comes to mind is “Why we need to understand how to properly handle errors?” There are at least two reasons:

  • Do not piss out the users. I know too many applications which fail without even telling the user what went wrong;
  • There is a category of applications which are very errors-sensitive. These are the applications which deal with huge financial

You can open any book on C# and see that for handling (or representing) any unfortunate situations such as validation fails, the critical system fails authors suggest to use exceptions.
The topic is a little bit holy war because there are no silver bullets, but it doesn’t mean that we don’t want to understand the problems which accompany exceptions using.
Several years ago, I asked Uncle Bob about the problems of using exceptions and he replied:

Continue reading