left-icon

SOLID Principles Succinctly®
by Gaurav Kumar Arora

Previous
Chapter

of
A
A
A

CHAPTER 5

Open-Closed Principle

Open-Closed Principle


When I first read about this principle, I thought it meant my class should be open or closed, either one or the other. But then I read the following definition from Wikipedia:

"Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification."

I was shocked, wondering how it was possible to make my classes both open and closed and not open and closed. In other words, I couldn’t understand how to allow things to be modified without doing actual modifications to my object. This sounds confusing, doesn’t it?

I dove into OOP for answers. Let's think about abstraction: We can create a base class and a function that can be overridden (i.e., functions with different behaviors). Yes, we can allow changes by keeping objects unchanged.

Next, let's take an example: We need to send emails using various operations. The bodies of emails depend on certain business rules and can contain the same or different messages. Now, what do we need to do here?

We can create a class like CreateEmail, or whatever you want to name it, with one method—BuildMessage. So, this class is only responsible for building email messages as per different logic. Because this method can be overridden, I can define its functionality as I choose.

What do you think of the example so far? Is it following OCP or not? In fact, we can’t say it is OCP compliant, but it is SRP compliant.

Let’s recall the problem-solving scenario we discussed in the preceding chapter. We need to import and sync data from different database servers after fetch, scan, and store operations. Finally, we need to save or persist the database on our servers.

Here are the steps we can draft:

  1. Contact external servers
  2. Sync/fetch data
  3. Analyze/scan data
  4. Store in temporary storage
  5. Save/persist database on local server(s)

Notice that in step 3 we need to analyze/scan the data. What does this mean? Is it something we need to validate our data? The simple answer is yes. Let’s elaborate on this.

We need to import and sync the database or data from external servers to the internal server.

During this operation, we should make sure that the correct and relevant data is being imported and synced. For that, we need to create specific rules—e.g., we need to update our development database server from our production/staging/preproduction database server. So, we need to make sure that the correct data is synced.

Consider a couple of real-time scenarios that could cause problems:

  • Our internal server has new tables but the external does not.
  • Our internal server's table XYZ column data type got changed from VarChar to nVarChar.

Note: Here we are discussing Microsoft SQL Server 2008R2, which is a Relational Database Management System (RDBMS) developed by Microsoft. Its primary function is to store and retrieve data as per requests coming from the same computer or from computers across a network. Refer to: https://en.wikipedia.org/wiki/Relational_database_management_system to learn more about RDBMS.

Let’s write a simple code snippet in Code Listing 10 that will implement the previous scenario.

Code Listing 10

namespace OCP_Violation

{

    public class ValidateData

    {

        public void SyncronizeData(ServerData data, SourceServerData sourceData)

        {

            if (IsValid(data, sourceData))

            {

                //First validate data and then send to persist.

            }

        }

        private bool IsValid(ServerData data, SourceServerData sourceData)

        {

            var result = false;

            if (data.Type == sourceData.Type)

                result = true;

            if (data.IP != sourceData.IP)

                result = true;

            //Other checks or rules to validate incoming data.

            return result;

        }

    }

}

Code Listing 11

namespace OCP_Violation

{

    public class SourceServerData

    {

        public string IP { get; set; }

        public string Type { get; set; }

        //Other stuff goes here.

    }

}

Code Listing 12

namespace OCP_Violation

{

    public class ServerData

    {

        public string IP { get; set; }

        public string Type { get; set; }

        //Other stuff goes here.

    }

}

What’s wrong with the preceding code snippet?

In order to find out, let’s revisit the class ValidateData, which was constructed in Code Listing 10.

Our class is doing two things:

  • Validating the data.
  • Saving the data.

Note: Remember, following OCP, classes shouldn’t allow for modifications, but they can be extendable.

Next, let’s check if Code Listing 13 follows OCP.

Code Listing 13

namespace OCP_Violation

{

    class Program

    {

        static void Main(string[] args)

        {

        }

    }

}

Unfortunately, this code is not following OCP. Our class ValidateData is open for new additions or operations, and if we want to extend it, that will be a tedious job.

Let’s think of a scenario in which we want to extend this class so that it can use another external service. In such a scenario, as developers we would have no choice but to modify the IsValid method. Also, if the class must be made a component and provided to third parties for use, these third-party users would have no way to add another service, and that would mean this class is not open for extensions. Also, if we need to modify the behavior in order to persist data, we would need to change the actual class.

To sum up, this class is directly violating OCP because it is neither open for extensions nor closed for modifications.

What does it mean to be open for extension?

It means that our module is flexible enough that we are able to extend its behavior in order to meet the requirements of the application properly.

In Code Listing 13, our validators should be extendable so that any kind of validate should fit with a single validator. We will discuss this further in the upcoming code snippet.

What does it mean to be closed for modification?

It means our code should be inviolate. The source code shouldn't allow changes to be made.

Again note that in Code Listing 13 our ValidateData class should not allow modification by external sources.

Is it possible to extend behavior without modifying code? We can extend or add our own implementation to the existing code with the use of abstraction. In C# language, we can use abstract classes or interfaces (however, accepting interfaces as pure abstraction depends on the situation—http://blog.ploeh.dk/2010/12/02/Interfacesarenotabstractions/).

Think abstraction

We are not going to discuss abstraction in detail, but as a review, remember that it is an OOP principle providing the facility to create abstract base classes so that we can perform abstraction (using language C#). So, derived classes automatically follow OCP by manipulating an abstraction. Here we can create new classes that are derivatives of the abstract classes, thus extending our modules or classes rather than modifying them.

Let’s revisit Code Listing 13. In it, we are simply validating the incoming data with our local data. The main intention here is to validate data. How many validators can you think of?

Unfortunately, our ValidateData class is trying to get IsValid(), then trying to persist it in the repositories, which is not a good way to perform. We can say that it’s not a good design.

Our next step would be to make our Code Listing 13 example OCP compliant.

This is a good time to rewrite our application or code.

Class Diagram OCP

Figure 4: Class Diagram OCP

From the beginning, let’s make the code purely abstract for DataValidator so that we need only to pass a type and it’ll go to validate.

First, let’s create an interface: IValidateData.

Code Listing 14

public interface IValidateData

{

    bool Validate(ServerData data, SourceServerData sourceData);

}

Here, our types of IValidateData classes or our classes that implement IValidateData must have the Validate() method.

In Code Listing 15, let’s create a class that implements the IValidateData interface.

Code Listing 15

using System.Collections.Generic;

using System.Linq;

namespace OCP_Follow

{

    public class ValidateData : IValidateData

    {

        private readonly IEnumerable<IValidator> _validators;

        public ValidateData(IEnumerable<IValidator> validators)

        {

            _validators = validators;

        }

        public bool Validate(ServerData data, SourceServerData sourceData)

        {

            return _validators.Any(validator => validator.IsValid(data, sourceData));

        }

    }

}

Wait, how does this follow OCP?

Here, we can extend the behavior of our validators (it is a type of IValidator). We have to stick with the Validate() method. In this scenario, we can’t modify the source code, but we can extend our validators as we are passing a list of validators of the IValidator type.

Code Listing 16 shows how we can define various validators.

Code Listing 16

namespace OCP_Follow

{

    public interface IValidator

    {

        bool IsValid(ServerData data, SourceServerData sourceData);

    }

}

We’ve defined a type of validator, so now let’s define our actual validators in Code Listing 17.

Code Listing 17

namespace OCP_Follow

{

    public class TypeValidator : IValidator

    {

        public bool IsValid(ServerData data, SourceServerData sourceData)

        {

            return data.Type == sourceData.Type;

        }

    }

}

Here, TypeValidator is a type of IValidator, which means there is nothing much to go beyond this type.

Tip: Keep these terms in mind while implementing OCP: abstraction, closures, extenders.

Let’s create a client in Code Listing 18 and see how our validators work.

Code Listing 18

using System;

using System.Collections.Generic;

using System.Linq;

namespace OCP_Follow

{

    class Program

    {

        static void Main(string[] args)

        {

            //Only for demonstration purposes.

            var sourceServerData = new List<SourceServerData>();

            var serverData = new List<ServerData>();

            foreach (var data in serverData)

            {

                var sourceData = sourceServerData.FirstOrDefault(s => s.RecordIdentifier == data.RecordIdentifier);

                var isValid = IsValid(data, sourceData);

                Console.WriteLine("Record with Id {0} is {1}", data.RecordIdentifier, isValid);

            }

            Console.ReadLine();

        }

        private static bool IsValid(ServerData data, SourceServerData sourceData)

        {

            List<IValidator> validators = AddValidatorsToValidate();

            IValidateData validateData = new ValidateData(validators);

            return validateData.Validate(data, sourceData);

        }

        private static List<IValidator> AddValidatorsToValidate()

        {

            return new List<IValidator>

            {

                new IPValidator(),

                new TypeValidator()

            };

        }

    }

}

The code snippet in Code Listing 18 is completely understandable. It does not require any further explanation. If you think differently, contact me, as I would love to discuss this.

In Code Listing 18, we have a ValidateData class that is responsible only for validating data by certain validations or rules.

With the changes made, our class is now more stable and robust; we can add as many validators as we want. We can also use this validator to save our data.

Another way we can save the data is by calling this validator from another class, and it could be a repository class or our own custom class in which we persist our data.

Conclusion

In this chapter, we examined the Open-Closed Principle, the second principle in SOLID, which tells us how to handle code base while interacting with or writing for external parties.

With the help of code examples, we have come to the conclusion that, according to SOLID principles, a good class is a class that does not allow code changes but does allow extension for functionalities. There can be more variations of the class, but there should not be a way for anyone to modify the code of an existing class.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.