Ibrahima Ciss

Posted on

# SOLID Principles in Swift: Open/Closed Principle

Last week we revised the Single Responsibility Principle or SRP, today let's have a look at the Open/Closed Principle or OCP, which states: "Entities should be open for extension, but closed for modification." Let's break this definition down.

• Entities, think of it as classes, structs or methods should be simple to change.
• Closed for modification, we want to change that entity's behavior without modifying its source code. This can be really problematic. How the heck can we change the behavior of the entity without touching its source code? And that's when we come back to the term extension. We should think about extensibility while writing our entities.

## Committing the sin

Without any further ado, let see an example of this principle being applied hoping it’ll click for you.
For that, let’s imagine we have a `Square` class with its properties like so:

``````class Square {

private(set) var width: Double
private(set) var height: Double

init(width: Double, height: Double) {
self.width = width
self.height = height
}

}
``````

Now, we say that we want to calculate the total area of squares. If we follow the Single Responsibility Principle, we might create a separated class that is dedicating to calculate that area:

``````class AreaCalculator {

func calculate(squares: Square...) -> Double {
return squares.reduce(0) { \$0 + \$1.width * \$1.height }
}

}
``````

If we run this, it works flawlessly:

``````let square1 = Square(width: 10, height: 10) // 100
let square2 = Square(width: 20, height: 20) // 400
let square3 = Square(width: 30, height: 30) // 900

let calculator = AreaCalculator()
calculator.calculate(squares: square1, square2, square3) // 1400
``````

Now let's say we want to calculate the circle's areas as well. Our first thought might be to create a `Circle` class.

``````class Circle {

}

}
``````

In the code above, we notice that we call everything square (in the `AreaCalculator` class) because we didn't think about the possibility that we'll need to calculate the area for anything other than a square. So we'll have to edit the original `AreaCalculator` class, meaning we broke the Open/Closed Principle.
Let's see how to change the behavior of the `calculate` method before refactoring it.

``````class AreaCalculator {

func calculate(shapes: AnyObject...) -> Double {
var totalArea: Double = 0
for shape in shapes {
if shape is Square {
let square = shape as! Square
totalArea += square.width * square.height
}
if shape is Circle {
let circle = shape as! Circle
}
}
}

}
``````

Swift being a static language, the `calculate` method has to accept an array of `AnyObject`, then we manually check the type of the object.
And if we run now the code, we have something like this:

``````let calculator = AreaCalculator()

let square1 = Square(width: 10, height: 10) // 100
let square2 = Square(width: 20, height: 20) // 400
let square3 = Square(width: 30, height: 30) // 900
let cirlcle1 = Circle(radius: 10) // 314.15926
let cirlcle2 = Circle(radius: 20) // 1256.6370

calculator.calculate(shapes: square1, square2, square3, cirlcle1, cirlcle2) // 2970.79632
``````

Like we said above, we clearly break the Open/Closed Principle by doing this. The `AreaCalculator` is opened for modification when it should be closed. However, you might be thinking, oh no, it's not a big deal, add a few `if` statements, and you're done, don't worry about this principle. Ok, fair enough, but let say we also need to calculate the area of a triangle, kite, hexagon, pentagon, and some other …gon shapes (screwed right 😅). Do you think we need to add endless if statements in the `calculate` method every single time we make a change? Of course no. But now, what can be the solution to make the `AreaCalculator` closed for modification 🤔?

## Coding to an interface (or protocol): the cure

In order to extend the behavior of the `AreaCalculator` while keeping it closed for modification, we need to separate that behavior behind an interface (or protocol in Swift) and then flip the dependencies around, as Uncle Bob said. It can be a little tricky at first to get this, but let's break it down step by step.

### Separating the external behavior behind an interface

To do that, we’ll create a `Shape` protocol like so:

``````protocol Shape {
var area: Double { get }
}
``````

The nice thing about Swift is we can have properties in Protocols or interfaces and this particularly reads well: a `Shape` has an `area` 😁
Now we need to implement or to conform to that interface in each concrete class.

``````class Square: Shape {

private(set) var width: Double
private(set) var height: Double

var area: Double {
return width * height
}

init(width: Double, height: Double) {
self.width = width
self.height = height
}

}

class Circle: Shape {

var area: Double {
}

}

}
``````

Step 1 is done, let’s focus on step 2 now.

### Flip the dependencies around

To do so, rather than accepting an `AnyObject` array in our `calculate` method, we’ll accept an array of `Shape`.

``````class AreaCalculator {

func calculate(shapes: Shape...) -> Double {
return shapes.reduce(0) { \$0 + \$1.area }
}

}
``````

Because we code to an interface and implement it to the `Square` and `Cercle` classes, we're 100% sure that they'll have an `area` property. We simply use it to calculate the sum of all shapes' area.
This is really powerful because if we need to calculate a triangle's area, for instance, we'll just have to create the `Triangle` class, make sure that it conforms to the `Shape` protocol, then set the `area` property along with the formula for calculating the area of a triangle, and we pass that into the `calculate` method of `AreaCalculator`.
Notice that we never ever touch the `AreaCalculator` class again because it already knows how to calculate shape areas.

## Conclusion

As a developer, we should strive to write collaborative code. What I mean by that is we should help our fellow colleague or friend to be able to extend the functionality of a system without needing to modify the source code of any existing classes. Yes, it can be difficult at first glance, but by applying these S.O.L.I.D. principles, we can achieve this goal easier. I hope this example makes something click for you for your understanding of the Open/Closed Principle. Next, we'll have a more in-depth look at the Interface Segregation Principle; until then, have a nice week, and may the force be with you 👊.