Tagged: .Net, C#, Coding Practices, Coding Right, Design Principles, Right design, SOLID
- This topic has 0 replies, 1 voice, and was last updated 2 years, 5 months ago by
Aruorihwo.
- AuthorPosts
- February 26, 2020 at 10:32 am #86687Spectator@aruorihwo
Part 1 of this article talked about Single Responsibility principle which is the ‘S’ in the popular SOLID Design Principles. This article will talk about the next principle – the Open/Closed principle. We will be discussing what that means practically, when it applies and when it does not. We will also be discussing how far we should take it. SOLID principles apply to any programming language, but we would be using C# for our examples in this article.
O – Open/Closed Principle
Open/Closed principle states that software entities such as classes, modules, functions and all must be open for extension but closed for modification. Robert C. Martin regarded the Open/Closed principle as “the most important principle of object-oriented design”
The idea of Open/Closed principle is very simple. You have a code that is successfully working in the production environment, but because of a change you want to make, you end up changing this code and introducing bugs to it. You have then endangered what you already have working because you want to make a modification. That is exactly what Open/Closed principle is trying to avoid – if it isn’t broken don’t modify it in such a way that new bugs are added to it. Let’s look at an example below
Perimeter Calculator
Let’s say we have a client called Ameyo. Ameyo has come to us that she requires a simple calculator for calculating the sum total perimeter of collection of triangles. This seems a fairly simple request. So, we first create a Triangle class.
public class Triangle { public double Base {get; set;} public double LeftSide {get; set;} public double RightSide { get; set; } }
Then, we create a PerimeterCalaculator class to handle the calculation of perimeter of the collection of triangles sent to the method as a parameter. A simple foreach would do to make our solution work beautifully.
public class PerimeterCalculator() { public double Calculate(Triangle[] triangles) { var totalPerimeter = 0; foreach(var triangle in triangles) { var perimeter = triangle.Base + triangle.LeftSide + triangle.RightSide; totalPerimeter += perimeter; } return totalPerimeter; } }
Ameyo is thoroughly impressed with our work and begins using the solution and getting the results she wants. But, the next week, she is back, asking if we could add rectangles to the list of shapes.
The truth is the request is no big deal. After all, we are geniuses. Our code might get a little more complicated, but we know we can pull it off. So, we add another class for rectangles called the Rectangle class.public class Rectangle { public double Length { get; set; } public double Breadth { get; set; } }
However, adding a new class does not solve the situation. We would then need to modify the Perimeter calculator class to accommodate the new shape that maybe passed as a parameter to the calculate method.
public class PerimeterCalculator() { public double Calculate(object[] shapes) { var totalPerimeter = 0; foreach(var shape in shapes) { if(shape is Triangle triangle) { var perimeter = triangle.Base + triangle.LeftSide + triangle.RightSide; totalPerimeter += perimeter; } if(shape is Rectangle rectangle) { var perimeter = rectangle.Length + rectangle.Breadth; totalPerimeter += perimeter; } } return totalPerimeter; } }
Now, we have the exact solution Ameyo wants and all is well once again. Until a week later, when she comes and asks if we can add circles to the shapes in which their perimeter is calculated.
I know it seems that to add an extra shape isn’t going to be that much of a big deal, but picture a real life scenario, where the calculations and processes are 100x this and changes are requested regularly, this would be a very big issue. Why? It is because our code violates the Open/Closed principle. Which implies that our code is not scalable or extensible. Also, we must keep making changes to an already existing and working code, a mistake would cause the whole house of cards to come crashing down. That is unacceptable in a production environment.
Applying the Open/Closed Principle
The Open/Closed principle usually sounds very academic and a bit abstract. But we have seen a scenario where we can apply the principle and save ourselves a lot of head and heart aches. This principle makes us endeavor to write our code such that it does not have to be changed every time requirements change. Sounds impossible? Well it isn’t. In C# and other similar languages, to apply this principle usually involves inheritance, polymorphism and implementing interfaces. Let’s see how we can use some of these concepts for our code.
The simplest way of applying this principle and solving this problem is to create a base Shape class that can be inherited and overridden if need be. This class would be the base for the shapes Ameyo has requested and any other shape Ameyo can request or create. Here’s how the Shape class looks:
public abstract class Shape { public abstract double Perimeter(); }
This is looks perfect! All other shape classes can then inherit from this base class and implement their own perimeter calculation.
public class Triangle : Shape { public double Base {get; set;} public double LeftSide {get; set;} public double RightSide { get; set; } public override double Perimeter() { var perimeter = Base + LeftSide + RightSide; return perimeter; } } public class Rectangle : Shape { public double Length { get; set; } public double Breadth { get; set; } public override double Perimeter() { var perimeter = Length + Breadth; return perimeter; } } public class Rectangle : Shape { public double Length { get; set; } public double Breadth { get; set; } public override double Perimeter() { var perimeter = Length + Breadth; return perimeter; } } public class Circle : Shape { public double radius { get; set; } public override double Perimeter() { var perimeter = 2 * Math.PI * radius; return perimeter; } }
The perimeter calculator class can then be light weight and extensible as shown below:
public double Perimeter(Shape[] shapes) { double perimeter = 0; foreach (var shape in shapes) { perimeter += shape.Perimeter(); } return perimeter; }
We can keep extending and extending the application to take as much shapes as we want. This is because we have moved the responsibility of calculating the perimeter from the PerimeterCalculator class to the individual shapes, thereby opening up our application to extensibility. This means, just like the Open/Closed principle, our application is closed for modification but open for extension.
Open/Closed Principle – Applying Balance
It is very usual for people to go overboard once the hear of a principle or technique, but it is very important for us to maintain balance. So, the question is, when do we use this principle?
Let’s use our example as a case study. It is very obvious that our first implementation of a solution to Ameyo’s requirements didn’t follow the principle. But, should it have been? I believe a general advice to this question is to examine the context. Are we expecting extensions to a module or solution from the client? You may know based on previous encounters with the client or with the problem, but I don’t advise you assume or anticipate requirement changes. If you do expect extensions, then apply the principle. Otherwise, I suggest you pay more attention to writing good and properly structured code that you can easily change if the requirements change. But once they change, you should definitely do the work required to make your code abide to the principle because requirements would most like change again in a similar way once they do change.
- AuthorPosts
- You must be logged in to reply to this topic.