SOLID Principles with Examples in iOS/Swift (part1)

SOLID principles are relatively old but incredibly useful concepts to apply to any OOP codebase in any language.

SOLID stands for Single Responsibility Principle, Open/Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle. These principles feed into and support each other and are one of the best general design approaches you could take for your code.

Let’s go through each of them:

Single Responsibility Principle:

The Single Responsibility Principle (SRP) is the most important one of them. It states that every module should have only one responsibility and reason to change. SRP starts with small concrete and specific cases such as a class and/or an object having only one purpose and being used only for one thing. The idea is that when, for example, you create a new model class called Post, its single purpose and responsibility is to hold the data and information about a post. It’s a model class, it should do no more, no less. It should not be accessing the database to save itself. It should not be creating underlying comments or changing them in any way. It should not be parsing JSON to create a new post out it. All of those things are single responsibilities of other objects that should not be mixed into that Post class. The Post class has only one reason to change — it changes when we need to change the data structure of our posts in our application. It should not change because we decided to swap the underlying database to Realm from Core Data or because our backend decided to return a different type of JSON.

Open/Closed Principle:

The Open/Closed Principle (OCP) states that your modules should be open for extension but closed for modification. It’s one of those things that sounds easy enough but is kind of hard to wrap your head around when you start to think about what it means. Effectively it means that when writing your code you should be able to extend the behavior of your objects through inheritance, polymorphism, and composition by implementing them using interfaces, abstractions, and dependency injection. If, let’s say, you have a PostsStorage class that has a certain interface that allows you to store Post models in the database. According to that principle, when you want to extend and add behavior and features to your PostsStorage, you should be able to do that through inheritance and through injecting new dependencies into that storage. For example, if you want to change the database that the storage saves posts to from Core Data to Realm you have two options: either you subclass from it and override methods that call Core Data and use Realm there instead or you inject a different database adapter/accessor dependency that complies to the same protocol as the Core Data one but uses Realm under the hood instead. In both scenarios though, every object that was previously using PostsStorage should still be able to use it as before without any changes because in both scenarios, the PostsStorage’s interface that they relied on hasn’t changed. We effectively extended PostsStorage behavior without modifying it. It nicely aligns with SRP because PostsStorage hasn’t had a reason to change when we swapped the underlying database to Realm; it was not PostsStorage’s responsibility to work with it in the first place.

Decorator Design pattern is mainly focused on Open/Closed Principle.

Decorator is a wrapper around another class that enhance its capabilities. It wraps around something that you want to decorate, implements its interface, and delegates messages sent to it to the underlying object or enhances them or provides its own implementation.

Let’s take a look at the following example:

protocol Product {
func price() -> Int
func name() -> String
}
class FullPriceProduct: Product {
    func price() -> Int {
return 1000
}
func name() -> String {
return "I'm a product"
}
}
class DiscountedProductDecorator: Product {
private let decoratedProduct: Product

init(decoratedProduct: Product) {
self.decoratedProduct = decoratedProduct
}
    func price() -> Int {
return Int(Float(decoratedProduct.price()) * 0.75)
}
    func name() -> String {
return decoratedProduct.name()
}
}
class CheckoutManager {
    func checkout(product: Product) {
let name = product.name()
let price = Double(product.price() / 100)
print("charging customer $\(price) for \(name)")
}

let fullPriceProduct = FullPriceProduct()
let discountedProduct = DiscountedProductDecorator(decoratedProduct:
let checkoutManager = CheckoutManager()
checkoutManager.checkout(product: fullPriceProduct)
checkoutManager.checkout(product: discountedProduct

Here we have a Product protocol that defines the interface all of our products will have. There’s aFullPriceProduct class that implements Product pro- tocol, and it is a simple model class, nothing to it. We also have a Checkout- Manager class, instances of which operate with objects that implement the Product protocol. The interesting thing is DiscountedProductDecorator. It is used to apply one or many discounts to a product. The way it works is it implements a Product interface and wraps around a decorated product object. It delegates all the messages sent to it to an underlying decoratedProduct and adds (“decorates with”) additional behavior in the price() method to ap- ply a discount to the resulting product price. At the end of the day you can wrap your objects in multiple decorators and use them just like the objects they decorate because they comply to the same protocol. The users of the Product protocol don’t have to know that they are working with a decorator that enhances the original object. We have complied with multiple SOLID principles again, especially the Open/Closed Principle.

Github Decorator Design Pattern Link:https://github.com/ansu/Swift-DesignPatterns/tree/master/Decorator.playground

Hope you like the article, in next article If you enjoyed it, feel free to hit the clap button below 👏 to help others find it! I would be writing reaming three SOLID Principles.