Friday, August 25, 2017

SOLID - 2. The Open/Closed Principle

In this article, I am describing the Open/Closed Principle. Another principle from the group of five principles that make up the SOLID acronym:


You can read up on my other articles on SOLID here:


The Open/Closed Principle

Its definition says: "Software entities (classes, modules, functions, etc.) must be open for extension but closed for modification."

When the software grows and different classes start to depend on each other, the risk of a change made in one class breaking code in some other class increases. The Open/Closed principle tries to address this problem.

To adhere to the OCP, your application should be build in a way that allows new functionality to be added to it without changing existing code, especially when the existing code is already used in multiple places in your application. 

Applications build without this principle in mind tend to become more and more fragile over their lifecycle until the cost of maintaining the application reaches a point at which it is just cheaper to rewrite it complelty.

Let's look at an example:

public class Actor { };

public class Player : Actor { };
public class Enemy : Actor { };

public class Playfield
{
    IList<Actor> _actors;
    Canvas _canvas;
    // ..

    public void DrawActors()
    {            
        foreach (Actor actor in _actors)
        {
            if (actor is Player)
            {
                // Code to draw the player sprite
            }
            else if (actor is Enemy)
            {
                // Code to draw the enemy sprite
            }
        }
    }
}

This is a code-snippet for a small game. There is a Player and an Enemy actor as well as a Playfield class for drawing the playfield to the screen. Different routines are required to draw the sprites for the Player and the Enemy actor which is handled by the “DrawActors” method.

This implementation violates the Open/Closed principle. Whenever we want to add new types of Actors to the game, the method DrawActors must be changed. It is not closed for modification. 

Let’s look at an implementation that follows the OCP:


public abstract class Actor
{
    public abstract void Draw(Canvas canvas);
}

public class Player : Actor
{
    public override void Draw(Canvas canvas)
    {
        // Code to draw the player sprite
    }
}

public class Enemy : Actor
{
    public override void Draw(Canvas canvas)
    {
        // Code to draw the enemy sprite
    }
}

public class Playfield
{
    IList<Actor> _actors;
    Canvas _canvas;
    // ..

    public void DrawActors()
    {            
        foreach (Actor actor in _actors)
        {
            actor.Draw(_canvas);
        }
    }
}

By moving the drawing logic to an abstract method of the actor base class, new actors can now be added to the game without the risk of breaking existing code. 


This implementation is much more robust and easy to extend.