• 4 hours
  • Easy

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 3/28/24

“O” for the Open/Closed Principle

The Open/Closed Principle

This principle is considered probably the most important of object-oriented design. The open/closed principle states that your code (classes, modules, functions, etc.) should be open for extension, but closed to modification

What does that mean? 

It means that you should be able to add new functionality without changing your existing code. It uses interfaces to allow different implementations of your code that you can substitute without changing the code that uses them.

The benefit of using an interface is that it introduces an additional level of abstraction, which enables loose coupling. Implementing interfaces are independent of each other and don’t need to share any code.

Example 1: Not Closed to Modification

Let’s consider an example of figuring out the area of a rectangle. First, you have a rectangle that has width and height.

public class Rectangle
{
    public double Width { get; set; }
    public double Height { get; set; }
}

Next, create an  Area  calculator that calculates the total area for a collection of rectangles:

public class AreaCalculator
{
    public double Area(Rectangle[] shapes)
    {
        double area = 0;
        
        foreach (var shape in shapes)
        {
            area += shape.Width * shape.Height;
        }
 
        return area;
    }
}

Great! It works! But, what happens if you want to know the area of a circle? Hmmm. You’ll have to modify the  Area  method to handle circles. 

So, modify the  Area  to accept a collection of objects instead of a  Rectangle  type. Then you can check the type of each object to figure out if it’s a rectangle or circle and calculate the area accordingly.

public double Area(object[] shapes)
{
    double area = 0;
    
    foreach (var shape in shapes)
    {
        if (shape is Rectangle)
        {
            var rectangle = (Rectangle)shape;
            area += rectangle.Width * rectangle.Height;
        }
        else
        {
            var circle = (Circle)shape;
            area += circle.Radius * circle.Radius * Math.PI;
        }
    }
 
    return area;
}

OK, now that’s working. But, what happens if you want to find the area of triangles? Ugh. You're going to have to modify the  Area  method again!

As you can see, this is breaking the open/closed principle, which states that your method should be closed to modification. Unfortunately, the  Area  method is not closed to modification and not open for extension.

Example 2: Utilize Contracts

Fix this by creating a contract, which requires a class that inherits it to utilize all the items defined. In C#, contracts are interfaces and abstract classes. 

Abstract Classes

With an abstract class, you can hide internal details and show only the functionality.

  •     An abstract class is never instantiated.

  •     Must contain at least one abstract method.

  •     Typically used to define a base class that other classes inherit.

  •     It can contain constructors.

  •     It can implement functions with non-abstract methods.

  •     Cannot support multiple inheritance.

  •     Can’t be static.

Here's an example of a C# program utilizing an abstract class:

using System;
 
public abstract class MyAbstract
{
    public abstract void myTest();
}
 
public class MyClass1 : MyAbstract
{
    public override void myTest()
    {
        Console.WriteLine("This is myTest from MyClass1.");
    }
}
 
public class MyClass2 : MyAbstract
{
    public override void myTest()
    {
        Console.WriteLine("This is myTest from MyClass2.");
    }
}
Interfaces

Like a class, interfaces can have methods, properties, indexers, and events. But they will only contain the declaration of the members. The implementation of an interface’s members is done by the class that implements the interface.

For our example, we are going to use an interface.

Let’s create an interface for shape:

public interface IShape
{
    double Area();
}

Now, our classes for  Rectangle  and  Circle  inherit the  IShape  interface:

public class Rectangle : IShape
{
    public double Width { get; set; }
    public double Height { get; set; }
    public double Area()
    {
        return Width * Height;
    }
}
 
public class Circle : IShape
{
    public double Radius { get; set; }
    public double Area()
    {
        return Radius * Radius * Math.PI;
    }
}

In doing so, we moved the responsibility of calculating the area away from the AreaCalculator’s  Area  method to each class.

How would I implement these classes in my application?

One way is to use the concept of dependency injection. Injection is the idea that an object is given (or "injected") an object to use, instead of making the object itself. You have the object that has the injection and a method that takes an interface as a parameter. That way, some other place in the code can instantiate an object that implements the interface and then uses that object for what gets injected.

In our application, instead of instantiating one of the two types of  IShape,  we will inject the one we want. Let's say our application has an MVC controller called ShapeController. We can add a parameter to the controller constructor, which is of type  IShape .  Then, we can pass in the correct specific implementation as that parameter. 

public ShapeController : Controller
{
    private IShape _shape;
    
    public ShapeController(IShape shape)
    {
        _shape = shape;
    }
}

 We still have to deal with one more detail. We need to create the actual  IShape  object to be injected into the  ShapeController. You will see how to do that in a later section of the course. 

Let’s Recap!

  • The open/closed principle states that your code (classes, modules, functions, etc.) should be open for extension, but closed to modification.

  • Using contracts like interfaces enables you to adapt the functionality of your application without changing existing code. 

  • An abstract class can’t be instantiated and can implement functions.

  • An interface contains only the declaration of methods, properties, indexers, and events.  

Let’s move on the third letter in SOLID, “L,” for the Liskov substitution principle.

Example of certificate of achievement
Example of certificate of achievement