SOLID Principles in Swift: Open/Closed Principle
Tags: solid, architecture, cleancode
Published: 2021-01-19 12:21:36

---
title: SOLID Principles in Swift: Open/Closed Principle
published: true
description: 
tags: #solid, #architecture, #cleancode
//cover_image: https://direct_url_to_image.jpg
---

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:
```swift
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:
```swift
class AreaCalculator {
  
  func calculate(squares: Square...) -> Double {
    return squares.reduce(0) { $0 + $1.width * $1.height }
  }

}
```
If we run this, it works flawlessly:
```swift
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.
```swift
class Circle {
  
  private(set) var radius: Double
  
  init(radius: Double) {
    self.radius = radius
  }
  
}
```
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.
```swift
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
        totalArea += circle.radius * circle.radius * Double.pi
      }
    }
    return totalArea
  }

}
```
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:
```swift
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:
```swift
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.
```swift
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 {
  
  private(set) var radius: Double
  
  var area: Double {
    return radius * radius * Double.pi
  }
  
  init(radius: Double) {
    self.radius = radius
  }
  
}
```
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`.
```swift
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 👊.