Andy Crouch - Code, Technology & Obfuscation ...

The Open/Closed Principle Real World Example

Code Displayed On Macbook

Photo: Unsplash

A regular discussion I have with junior developers is about loosely coupled code. Why especially with .Net, it is important and the benefits it can bring. I cover test-ability, multiple inheritance and polymorphism. I instil the SOLID principles in them and offer guidance when reviewing their code. Developers sometimes say it feels like they are over-engineering a solution. I argue that experience has taught me that it will pay off at some time.

One of the biggest points of discussion is the Open/Closed principle and how it can be implemented in real code. Adhering to this principle can be done in many ways depending on the situation. Finding good teaching examples in your own project can sometimes be hard.

This week some code that I wrote 2 years ago finally generate a good example for me to use. It came in the guise of a service class that used an abstraction to retrieve a List<Model>. When developing the original code there were two requirements:

  • Create the list, pre-populate initial with data from the database and calculate the remaining fields. This data is then persisted to the database.

  • Read the previously calculated data from the database if it exists.

At the time I created two implementations of the interface to meet the criteria. I then used a Factory class to return the implementations as a SortedList. This was so that the Service Class could decide how to obtain the data. The Factory design pattern can be read up on here and here. The pattern’s goal is to allow code to defer instantiation of concrete objects to subclasses?

In our solution, we make use of the Ninject Dependency Injection framework throughout. While you can find extensions to create factories for your abstractions, I find it easier to use a factory class. You still inject your dependency’s into the factory. You then use them to create your concrete classes.

First off I defined the service interface

public interface IService
{
    IEnumerable<Model> GetModelListFor(Customer customer);
}

I then implemented the two service classes that I needed to meet the requirements. One to generate the model list and one to read the data if it exists in the database and create the models.

I then defined the factory class.

public interface IServiceFactory
{ 
    SortedList<int, IService> GetPrioritisedServiceList();
}

The implementation of the method is interesting. I used a SortedList return the concrete service implementations. This way I can state the order in which the client code transverses the services returned.

public SortedList<int, IService> GetPrioritisedServices()
{
    SortedList<int, IService> sortedServiceList = new SortedList<int, IService>();
    
    sortedServiceList.Add(0, GetInitialisedDatabaseService());
    sortedServiceList.Add(1, GetInitialisedCalculatedService());
    
    return sortedServiceList;
}

The service that wants to get the data call’s the factory’s GetPrioritisedServices(). It iterates over the services calling GetModelListFor(customer) method on each service. Once it obtains data it exists the loop.

The reason that this code becomes a great example was due to an extra requirement. We needed to be able to generate an example list of data based on some hard-coded values. The developer assigned to the work proposed a couple of different ways of making the change. Each involved making changes to the service class to generate and process the fake data through the workflow. I showed them that there was no need. That the abstraction was still open for modification without having to change it.

We created a new IService derived class. We implemented the hardcoded data in the GetModelListFor() method. We generated a dummy partial list which can be used as a basis for the existing calculations. We then updated the factory so that it returned the new service.

sortedServiceList.Add(2, GetInitialisedExampleDataService());

A nice simple solution which didn’t involve any changes to the service class. The solution isn’t quite right as the subsequent conversation with the developer showed. It would be a worthwhile change to actually have the factory use reflection to create the services based on the abstraction. This is a change that is now being made.

I’d be interested to hear your thoughts on this approach with suggestions for alternative ways to achieve the requirements. Reach out to me on twitter or email.