Building S.O.L.I.D - Open–Closed Principle

The second principle in S.O.L.I.D is the Open–Closed Principle.

The Open–Closed Principle states that classes should be open to extension but closed to modification. In modern object-oriented languages, this principle is best described through the notion of an abstract base class.

In a base class, we are able to implement shared functionality between child classes, functionality we expect not to change. In practice, this may be a task such as abstracting a data access layer (DAL) such as in a repository pattern. In the repository pattern, data access is standardized into generic CRUD operations. An example in C# is below, with implementations removed for brevity. A full implementation of the repository pattern can be found at this address.

/*
    Generic entity interface.
*/
public interface IEntity<T>
    {
        int ID { ... }
    }

/*
    Generic Repository interface for all entity types.
*/
public interface IRepository<T>
    {
        void Insert(T entity);
    void Update(T entity);
        void Delete(T entity);
        IQueryable<T> GetAll();
        T GetById(int id);
    }

/*
    Concrete implementation of the generic Repository for all entity types.
*/
public class Repository<T> : IRepository<T> where T : class, IEntity
    {
        protected Table<T> DataTable;

        public Repository(DataContext dataContext) { ... }

        public void Insert(T entity) { ... }

        public void Update(T entity) { ... }

        public void Delete(T entity){ ... }

        public IQueryable<T> GetAll() { ... }

        public T GetById(int id){ ... }
    }

In a child class, we are able to extend the functionality of a base class without having to make changes to the base class itself. Using our repository pattern example, we may have a ReportRepository class that implements our abstract GenericRepository base class. If we needed to implement a generic modification to all reports, we could override the Insert function to do just that. By overriding, we aren't having to modify the base class implementation. We are also able to extend the base class by adding our own additional functions (such as the case with FindByReportName).

/*
    Additional functions exposed for Report Repositories
*/
public interface IReportRepository<T>
    {
        void FindByReportName(String name)
    }

/*
    Concrete implementation of the generic Repository for Report entity types.
*/
public class ReportRepository : Repository<Report>, IReportRepository
    {
        public ReportRepository(DataContext dataContext) : base(dataContext) { ... }

    /*
        As this class inherits from the Repository class, so we are able to 
        make use of the CRUD functions already created for us.
    */

        public void Insert(Report entity) { 
        /*
            We are also able to extend the Repository class in our concrete
            Report Repository implementation if we need to do something 
            specific for Report types without modifying the base class. 
        */
    }

        public void FindByReportName(String name) { 
        /*
            We are also able to extend the Repository class with additional
            functionality without having to add to our base class.
        */
    }

    }

In review, the Open/Closed Principle promotes extensibility over modification, and will lead to code that is:

  • DRY-er (less repeated code)
  • Easier to maintain
  • Easier to test
  • Safer to change

This article is my 2nd oldest. It is 494 words long, and it’s got 0 comments for now.