Implementing the Open-Close Principle in C#

The Open-Close Principle

The Open-Close Principle states that software entities (classes, object, module, etc.) should be open for extensions, but closed for modifications. So, what does the principle mean to us?  Basically, it says that if you created a class, you do not change the internals of the class (close for modifications) if you need to add more functionality (open for extensions). To be clear, this does not mean that you will not change the internals of the class if there is a bug in the code, it only applies if you need to add more functionality.

A good example is a class that has 2 properties, first name and last name, and now you need to add the full name property that concatenated both first and last name properties.  Another example is a repository class that now needs some sort of sorting, caching or serialization mechanism.

In C#, we can accomplish this in 3 different ways:

Extension Methods

Extension methods is a good way to implement the open/close principle since we can add more functionality to a class (open to extensions), and we do not affect the original contract of the class (close to modifications).  It allows us to define new methods or extend the functionality of the class.

For example, using the above example of adding the full name property to a given class we can have this:

 

    public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

If this class is already in use, and if we add a new property, we can mess with caller methods, specially if we are using it for serialization.

To create an extension method you need to create another class with a static method that use the “this” keyword as part of the first parameter. The “this” keyword will refer to the actual class you want to extend.

 

    public static class ExtensionMethods
    {
        public static string FullName(this Person person)
        {
            return person.FirstName + ' ' + person.LastName;
        }
    }

When using the person variable in this method, we can have access to all public methods and properties of the object.

The Decorator Pattern

When using the Decorator Pattern, we can hide an object inside another object that use the same behavior (close for modifications) but internally it changes the implementation (open for extensions). A good example will be a repository class that needs now to do sorting without changing any of the signatures of its methods.

To use the Decorator Pattern, we have five steps:

  1. Have a base class or interface that define our implementation.
  2. Have a basic implementation of the base class or interface.
  3. Create a new decorator class, that you will use to add the new functionality and that implements the defined base class or interface.
  4. Pass the basic implementation object that we want to decorate on the decorator class constructor.
  5. Implement the base class or interface functionality adding the new logic to the basic class inside the decorator class.

In this example we want a repository for the Person class that will have a simple implementation of CRUD functionality.  A basic functionality interface can be:

 

    public interface IPersonRepository
    {
        IEnumerable Get();
        void Add(Person person);
        void Delete(Person Person);
        void Update(Person person);
        Person Get(int id);
    }

The basic implementation of this interface can be:

 

    public class PersonRepository : IPersonRepository
    {
        private List persons = new List
        {
            new Person {Id = 1, FirstName = "Han", LastName = "Solo" },
            new Person {Id = 2, FirstName = "Leia", LastName = "Organa" },
            new Person {Id = 3, FirstName = "Luke", LastName = "Skywalker" },
            new Person {Id = 4, FirstName = "Lando", LastName = "Carlissian" }
        };

        public IEnumerable Get()
        {
            return persons;
        }

        public void Add(Person person)
        {
            var maxId = persons.Max(p => p.Id);
            person.Id = maxId += 1;
            persons.Add(person);

        }

        public void Delete(Person person)
        {
            var oldPerson = Get(person.Id);
            if (oldPerson != null)
            {
                persons.Remove(oldPerson);
            }
        }

        public void Update(Person person)
        {
            var oldPerson = Get(person.Id);
            if (oldPerson != null)
            {
                oldPerson.FirstName = person.FirstName;
                oldPerson.LastName = person.LastName;
            }
        }

        public Person Get(int id)
        {
            return persons.SingleOrDefault(p => p.Id == id);
        }
    }

Now, we need to be able to add the sorting functionality. We create a new class, aka the decorator class, that implements the IPersonRepository interface and receive a IPersonRepository as part of its constructor. In this example we can create a SortedPersonRepository like this:

 

    public class SortedPersonRepository : IPersonRepository
    {
        private IPersonRepository _personRepository;
        private IOrderedEnumerable<Person> _sortedPersons;

        public SortedPersonRepository(IPersonRepository personRepository)
        {
            if(personRepository == null)
            {
                throw new ArgumentNullException("personRepository", "personRepository is null");
            }

            _personRepository = personRepository;
            _sortedPersons = _personRepository.Get().OrderBy(p => p.LastName);
        }

        public void Add(Person person)
        {
            _personRepository.Add(person);
            _sortedPersons = _personRepository.Get().OrderBy(p => p.LastName);
        }

        public void Delete(Person person)
        {
            _personRepository.Delete(person);
            _sortedPersons = _personRepository.Get().OrderBy(p => p.LastName);
        }

        public IEnumerable<person> Get()
        {
            return _sortedPersons;
        }

        public Person Get(int id)
        {
            return _personRepository.Get(id);
        }

        public void Update(Person person)
        {
            _personRepository.Update(person);
            _sortedPersons = _personRepository.Get().OrderBy(p =&gt; p.LastName);
        }
    }

As you can see, the SortedPersonRepository Class implements the IPersonRepository interface and pass an implementation of the IPersonRepository interface in its constructor. To use the decorator class, aka the SortedPersonRepository, we do as follows:

 

        static void Main(string[] args)
        {
            var unsortedRepository = new PersonRepository();
            var unsorted = unsortedRepository.Get();

            Console.WriteLine("Unsorted Records");
            DisplayNames(unsorted);

            var sortedRepository = new SortedPersonRepository(unsortedRepository);
            var sorted = sortedRepository.Get();

            Console.WriteLine("Sorted Records");
            DisplayNames(sorted);

            Console.Read();
        }

As you can see, when we instantiate the SortedPersonRepository object we are passing a PersonRepository object to it via its constructor.

With this implementation, we are preserving the basic CRUD functionality of the interface, but with the addition of the sorting capabilities in the decorator class.

 

The Adapter Pattern

When we use the Adapter Pattern, we are building a bridge between one code implementation and another code implementation.  Usually we explain the Adapter Pattern using the following quote: “You do not connect a lamp connecting the cable directly to an electrical socket”.  You will need a plug, in other words an adapter, between the cable and the socket.  In programming terms, if you have an implementation of 4 methods (close for modifications) and now you need to add another implementation because a third-party API needs it (open for extensions), how would you resolve this situation? You can use the Adapter Pattern.

To explain this pattern we will continue with the Person Repository sample.  In this case, the third-party API implements the repository as follows:


    public interface IAdaptedRepository
    {
        IEnumerable&amp;amp;amp;amp;lt;Person&amp;amp;amp;amp;gt; GetAll();
        Person Get(int id);
        void Add(string firstName, string lastName);
        void Delete(int id);
        void Update(int id, string firstName, string lastName);
    }

The difference between this interface and the IPersonRepository are as follows:

  • The method to get all the Persons in the IPersonRepository is called Get(), in the IAdaptedRepository is called GetAll().
  • The Add, Update, Delete methods in the IPersonRepository receive a Person object, while in the IAdaptedRepository they receive and id,  a first name and/or a last name.

To implement the Adapter Pattern we will need three steps:

  1. Implements the base class or interface of the functionality we want to add.
  2. Create a new adapter class that will receive our own implementation as part of its constructor.
  3. Implements the interface methods according to the new design.

We can accomplish this as follows:

    public class AdaptedPersonRepository : IAdaptedRepository
    {
        private IPersonRepository _personRepository;

        public AdaptedPersonRepository(IPersonRepository personRepository)
        {
            if (personRepository == null)
            {
                throw new ArgumentNullException("personRepository");
            }

            _personRepository = personRepository;
        }

        public void Add(string firstName, string lastName)
        {
            var person = new Person { FirstName = firstName, LastName = lastName };
            _personRepository.Add(person);
        }

        public void Delete(int id)
        {
            var person = new Person { Id = id };
            _personRepository.Delete(person);
        }

        public Person Get(int id)
        {
            return _personRepository.Get(id);
        }

        public IEnumerable<person> GetAll()
        {
            return _personRepository.Get();
        }

        public void Update(int id, string firstName, string lastName)
        {
            var person = new Person { Id = id, FirstName = firstName, LastName = lastName };
            _personRepository.Update(person);
        }
    }

Then, to use the adapter class we can code as follows:

            var repository = new PersonRepository();
            var adaptedRepository = new AdaptedPersonRepository(repository);
            var result = adaptedRepository.GetAll();

As you can see we can accomplish the Open-Close principle in C# using any of this three implementation. I personally can say that using extension methods is the simplest way to achieve it. The Decorator and the Adapter Patterns are useful if the new functionality will alter significantly the current implementation of our code.

If you need to see the whole code for this post please go to my GitHub account.

Rodnney

I am a "multi hat" software developer with more than 18 years in experience. I had worked from government agencies, insurance companies, ticketing system and educational business using a lot of technologies like SQL Server, Oracle, ASP.NET, MVC, HTML, CSS, Javascript, Linq, etc.

You may also like...

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.