The Open-Closed Principle (OCP)

A software artifact should be open for extension but closed for modification.

Bertrand Meyer coined the OCP in 1988, providing us with guidance on how to create a design that is stable in the face of changes.

Here, we're going to borrow the DrawAllShapes example from Agile Software Development: Principles, Patterns, and Practices, to explain this principle here. We will write the example in ES6; the unrelated details have been omitted:

class Circle extends Shape {
constructor(radius, point) {
this.type = 'circle'
// ...
}
}

class Square extends Shape {
constructor(width, point) {
this.type = 'square'
// ...
}
}

function drawCircle(circle) {
// Draw circle logic here
}

function drawSquare(square) {
// Draw square logic here
}

function drawAllShapes(shapes) {
shapes.forEach((shape) => {
if (shape.type === 'circle') {
drawCircle(shape)
} else if (shape.type === 'square') {
drawSquare(shape)
}
})
}

As you can see, we have the Circle and Square classes, which extend Shape. Both of them have field types. drawCircle() and drawSquare() are two functions that are responsible for drawing circles and squares, respectively. Now, let's examine the drawAllShapes() function. It goes through an array of shapes and check each shape's type and call with the corresponding draw function to draw that shape. This drawAllShapes() function violates the OCP because it is not extendable without modification when we need to support new kinds of shapes. It is not closed against new changes.

We can refactor it to the following to make it conform to the OCP:

class Circle {
//...
draw() {
// Draw circle logic here
}
}

class Square {
//...
draw() {
// Draw square logic here
}
}

function drawAllShapes(shapes) {
shapes.forEach((shape) => {
shape.draw()
})
}

As you can see, we move the draw function to each type of shape, and inside drawAllShapes() function, we simply call the draw() method of that shape. Now, the drawAllShapes() function supports Triangle, Rectangle, and any other type of shape, as long as it has a draw() function. This design now conforms to the OCP. It is changed by adding new code, such as Triangle and Rectangle, rather than changing existing code.

However, it is not really closed. It is closed to the requirement that needs to support a new type of shapes, but it is not closed against requirements, for example, if we need the application to draw all circles before drawing any square or we need to draw shapes in the order of their positions.

So, should we refactor our drawAllShapes() function to make it closed to the two requirements we just mentioned? No, not yet! We wait until the changes happen. That is, we only refactor the function when the change is needed. If it is still in the future, let's just wait for it. Because, as mentioned earlier, in an Agile project, changes happen frequently. Those requirements might be moved to the very end of the product backlog or might even no be longer needed. Refactoring it now will be an act of overdesign and the code will show the opacity symptom, not to mention the development time and effort to refactor it could be wasted.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.90.182