Solid Principles - Swift Flashcards
Swift :
Single Responsibility Principle Example 1
According to this principle, a class should have a reason, and only one, to change. That is, a class should only have one responsibility.
Let’s see an example:
**class LoginUser { func login() { let data = authenticareUserViaAPI() let user = decodeUser(data: data) saveToDB(array: array) }
private func authenticareUserViaAPI() -\> Data { // Call server to authenticate and return user's info }
private func decodeUser(data: Data) -\> User { // Decode data (Codable protocol) into User object }
private func saveUserInfoOnDatabase(user: User) { // Save User info onto database } }**
As noted, this class presents three responsibilities: Authentification, Decode info and Save info. To fulfill the principle of sole responsibility we extract each of these responsibilities in other smaller classes.
**class LoginUser {
let oAuthHandler: OAuthHandler
let decodeHandler: DecodeHandler
let databaseHandler: DataBaseHandler
init(oAuthHandler: OAuthHandler, decodeHandler: DecodeHandler, databaseHandler: DataBaseHandler) {
self.oAuthHandler = oAuthHandler
self.decodeHandler = decodeHandler
self.databaseHandler = databaseHandler
}
func login() { let data = oAuthHandler.authenticareUserViaAPI() let user = decodeHandler.decodeUser(data: data) databaseHandler.saveUserInfoOnDatabase(user: user) } }**
**class OAuthHandler { func authenticareUserViaAPI() -\> Data { // Call server to authenticate and return user's info } }**
**class DecodeHandler { func decodeUser(data: Data) -\> User { // Decode data (Codable protocol) into User object } }**
**class DataBaseHandler { func saveUserInfoOnDatabase(user: User) { // Save User info onto database } }**
Swift :
Single Responsibility Principle Example 2
The single responsibility sounds deceivingly simple: One class should only have one responsibility. A UserFetcher class that fetches new users. A Payment class that processes purchases in your app. A LocationTracker app that tracks your users location. These are all examples of classes with a single responsibility.
Because they do one thing, these classes are very simple. Just by reading their names, you know exactly what they do. They’re also easy to write: knowing exactly what the class is for means you know which methods you need and which of them need to be exposed as public methods.
They’re also much easier to maintain. The code of large classes with lots of things to do often gets intertwined like earbuds that have been in your pocket for too long. In order to change or fix something, you first have to meticulously untangle each of the responsibilities just to start thinking about where the problem is.
We often violate SRP because it’s easy. The biggest problem is the phenomenon of one little feature. It’s a trap we constantly keep falling into: I’ll just add this one little feature to the class. If you add that one little feature five times, suddenly it’s five little features. With each feature you are exponentially adding more and more complexity. Before you know, you created a huge, tangled mess.
iOS developers are one of the worst classes of programmers when it comes to following SRP. Case and point: our good old friend, the view controller.
What does a UIViewController do? Well, it assembles the views on the screen. It also prepares table view cells. Oh, it also navigates to another screen. Sometimes it also calls network requests. It also, also, keeps track of the state of our screen. A typical UIViewController is about 12 responsibilities removed from SRP. (This problem is often referred to as Massive View Controller.)
This is why view controllers often become those classes that everyone is afraid to touch, and nobody understands what’s going on. There are just too many of those one little features.
What can I do?
First off, stop adding little features. Shift your brain to thinking about modules, components and APIs. Get out of the mindset of patching and hacking, and into the mindset of creating libraries. Write small classes that do one thing. If you have a large problem, break it down. Write classes for each subset of the problem, and then write another one that will use those classes.
Identify the responsibilities of your view controller, and split them up. The easiest candidates are table view delegates and data sources. Make a separate class that does that thing. The data source can be any class, not just the view controller.
And always pay attention to the size of your classes. The number of lines can be a very good warning sign that some things should probably be split up.
Swift :
Open/Closed Principle Example 1
According to this principle, we must be able to extend the a class without changing its behaviour. This is achieved by abstraction.
class Scrapper {
func scrapVehicles() {
let cars = [
Car(brand: “Ford”),
Car(brand: “Peugeot”),
Car(brand: “Toyota”),
]
cars.forEach { car in
print(car.getScrappingAddress())
}
let trucks = [
Truck(brand: “Volvo”),
Truck(brand: “Nissan”),
]
trucks.forEach { truck in
print(truck.getScrappingAddress())
}
}
}
**class Car { let brand: String**
init(brand: String) {
self.brand = brand
}
func getScrappingAddress() -> String {
return “Cars scrapping address”
}
}
**class Truck { let brand: String**
init(brand: String) {
self.brand = brand
}
func getScrappingAddress() -> String {
return “Trucks scrapping address”
}
}
For each new type of vehicle, the getScrapingAddress() function must be implemented again, which breaks the principle of open/closed. To solve this point, we introduce the Scrappable protocol that contains the getScrappingAddress() method:
**protocol Scrappable { func getScrapingAddress() -\> String }**
class Scrapper {
func getScrapingAddress() {
let vehicles: [Scrappable] = [
Car(brand: “Ford”),
Car(brand: “Peugeot”),
Car(brand: “Toyota”),
Truck(brand: “Volvo”),
Truck(brand: “Nissan”),
]
vehicles.forEach { vehicle in
print(vehicle.getScrapingAddress())
}
}
}
**class Car: Scrappable { let brand: String**
init(brand: String) {
self.brand = brand
}
func getScrapingAddress() -> String {
return “Cars scrapping address”
}
}
**class Truck: Scrappable { let brand: String**
init(brand: String) {
self.brand = brand
}
func getScrapingAddress() -> String {
return “Trucks scrapping address”
}
}
Swift :
Open/Closed Principle Example 2
While SRP is deceivingly simple, the open/closed principle sounds more complex than it really is. It goes as follows: software entities should be open for extension, but closed for modification. Sounds pretty fancy, right?
What that sentence is trying to say is that it should be easy to add new features to your class. Earlier, I mentioned an example of a UserFetcher class as an example of SRP. Let’s say that class has one method, fetchUsers, which fetches a JSON of all your users from a database, parses it, and returns it.
class UserFetcher {
func fetchUsers(onComplete: @escaping ([User])-> Void) {
let session = URLSession.shared
let url = URL(string: “”)!
session.dataTask(with: url) { (data, _, error) in
guard let data = data else {
print(error!)
onComplete([])
return
}
let decoder = JSONDecoder()
let decoded = try? decoder.decode([User].self, from: data)
onComplete(decoded ?? [])
}
}
}
But hold on! A super-intelligent race of beings from Mars has invaded earth and taken you hostage! They want you to make the app work with Martian objects, instead of User objects. And they want it fast!
With our UserFetcher class, we need to change the whole implementation, even to the point of renaming the class, for it to work with Martians. If we took a different approach, this change would be a lot easier. What if instead of a UserFetcher, we wrote a generic Fetcher, for any Decodable list of things?
class Fetcher {
func fetch(onComplete: @escaping ([T])-> Void) {
let session = URLSession.shared
let url = URL(string: “”)!
session.dataTask(with: url) { (data, _, error) in
guard let data = data else {
print(error!)
onComplete([])
return
}
let decoder = JSONDecoder()
let decoded = try? decoder.decode([T].self, from: data) onComplete(decoded ?? [])
}
}
}
typealias UserFetcher = Fetcher
The implementation is almost the same, except we changed where we mention User to a generic T, which conforms to decodable. With this approach, we only need to change that single line at the bottom to support Martians.
typealias MartianFetcher = ListFetcher
As you can see, following the open/closed principle might just save your life one day!
Okay, the example with the Mars attack might be a little over the top. But if there’s one thing that’s constant about requirements, it’s that they change. You need to help your future self and allow for easy ways to respond to design changes in the future.
Swift :
Liskov Substitution Principle Example 1
This principle, introduced by Barbara Liskov in 1987, states that in a program any class should be able to be replaced by one of its subclasses without affecting its functioning.
For example, suppose we have a class, UserService, which has the responsibility of contacting users (for example, sending an email). If we change the business logic and, for example, we can only send mails to users over 17 years of age, we can create a subclass that adds the new business logic:
**class UserService { func contact(user: User) { // Retrieve user from database } }**
**class ValidUserService: Handler { override func contact(user: User) { guard user.age \> 17 else { return }**
super.contact(user: User)
}
}
In this case, the Liskov substitution principle is not fulfilled, since the subclass adds a condition (that the age of the user is over 17 years old), which a customer of the UserService class would not expect. We can solve this problem by not creating the subclass, and adding the precondition to UserService (including a default value):
**class UserService { func contact(user: User, minAge: Int = 0) { guard user.age \> minAge else { return } // Retrieve user from database } }**
Swift :
Liskov Substitution Principle Example 2
Here’s one principle that sounds very mathy and academic. If you think that name is bad, it has another one: substitutability. As in, the ability to be substituted with something else. And that’s exactly what this principle means.
LSP says that any function working with a class should work also with any of those class’ subclasses. This principle is a note of how you override methods. The user of that method shouldn’t be able to see a difference between your version and the base class’ method.
Let’s go back to our MartianFetcher. The Martians are happy with how the code works on Earth, but they don’t like how it works when they go back to Mars: there’s no internet there, so they don’t see any data. They want offline mode.
So, you go ahead and make a subclass of the Fetcher, which fetches data from a file on the device which contains cached Martians. This is a good idea since creating a subclass means you don’t have to change code that works with Fetcher, since the API stays the same.
You’re also really scared, so you take your keyboard and you quickly hack that feature into the app.
class FileFetcher: ListFetcher {
override func fetch(onComplete: @escaping ([T])-> Void) {
let json = try? String(contentsOfFile: “martians.json”)
guard let data = json?.data(using: .utf8) else {
return
}
let decoder = JSONDecoder()
let decoded = try? decoder.decode([T].self, from: data)
onComplete(decoded ?? [])
}
}
In your rush, you made a mistake. Here’s how the base class’ method works: if there is an error, the method calls the completion handler with an empty array. Your version works a bit differently. If there is an error, nothing happens. This means your UI won’t update if there is an error. The Martians are angry now.
There are two ways you can fix it, and one of them is wrong. You can go into your view controller and check whether your Fetcher is actually a FileFetcher and update the UI.
fetcher.fetch { martians in
self.martians = martians
self.tableView.reloadData()
}
if fetcher is FileFetcher {
tableView.reloadData()
}
This is wrong. It’s wrong because the whole point of a subclass is that you don’t have to change the user of the class, but only the subclass. If for every subclass we had to have a different way of using it, then inheritance would be completely pointless. (Same goes for protocols and composition!)
The correct way to fix this would be to align the overridden method to work like the base class’ method, or create a whole new class.
Whenever you’re checking if an instance is of a specific type, it’s probably code smell, and a violation of LSP. And most likely there’s an easier way to do things.
Swift :
Interface Segregation Principle Example 1
The Principle of segregation of the interface indicates that it is better to have different interfaces (protocols) that are specific to each client, than to have a general interface. In addition, it indicates that a client would not have to implement methods that he does not use.
For example, we can create an interface for animals that includes displacement methods:
**protocol AnimalProtocol { func walk() func swimm() func fly() }**
**struct Animal: AnimalProtocol { func walk() {} func swimm() {} func fly() {} }**
**struct Wale: AnimalProtocol { func swimm() { // Walw only needs to implement this function // All the other functions are irrelavant }
func walk() {}
func fly() {} }**
However, although Wale adopts the protocol, there are two methods that he does not implement. The solution is to establish three interfaces (protocols), one per method:
**protocol WalkProtocol { func walk() }**
**protocol SwimmProtocol { func swimm() }**
**protocol FlyProtocol { func fly() }**
**struct Wale: SwimmProtocol { func swimm() {} }**
**struct Crocodile: WalkProtocol, SwimmProtocol { func walk() func swimm() {} }**
Swift :
Interface Segregation Principle Example 2
The Martians are generally happy with your app but they want more features. They want to see the details of a Martian when they click on it. Being the protocol-oriented programmer you are, you create a protocol to deal with this problem.
protocol MartianFetcher {
func getMartians(onComplete: ([Martian])-> Void)
func getMartian(id: String, ([Martian])-> Void)
}
You create a class that implements this protocol, and hook it up to your app. But here’s the problem: The list screen doesn’t need getMartian, and the details screen doesn’t need getMartians.
Having methods available that you don’t need adds clutter and noise, making it harder to work with the API. It also leads to all the problems discussed in the Single Responsibility Principle section.
You can make a very easy fix that solves this problem. Just make two different protocols.
protocol MartiansFetcher {
func getMartians(onComplete: ([Martian])-> Void)
}
protocol MartianFetcher {
func getMartian(id: String, onComplete: ([Martian])-> Void)
}
You don’t actually have to change your implementation, the same class could implement both of these protocols. But in your list view controller you will use an instance of MartiansFetcher, without extra clutter. This lets you add functionality to the class that fetches Martians without complicating things for the users of the class.
This is the reason why Swift has Decodable, Encodable and Codable. Not everyone can conform to all, and not everyone needs all functionality. Apply SRP to your protocols as well as classes.
Swift :
Dependency Inversion Principle Example 1
According to the Dependency inversion principle:
- High level classes should not depend on low level classes. Both should depend on abstractions.
- Abstractions should not depend on the details. The details should depend on the abstractions.
This principle tries to reduce the dependencies between modules, and thus achieve a lower coupling between classes.
Let’s look at the following example:
**class User { var name: String
init(name: String) {
self.name = name
}
}**
**class CoreData { func save(user: User) { // Save user on database } }**
**class UserService { func save(user: User) { let database = CoreData() database.save(user: user) } }**
What happens if instead of using CoreData to save the data, we want to use the Realm database? The instantiation of the class as we have done in the example generates a strong coupling, so if we want to use another database, we would have to redo the code.
To solve this, when a database layer was established.
protocol Storable { }
extension Object: Storable { } // Realm Database
extension NSManagedObject: Storable { } // Core Data Database
**protocol StorageManager { /// Save Object into Realm database /// - Parameter object: Realm object (as Storable) func save(object: Storable) }**
Now we have User that adopts the Storable protocol and the UserService class adopts the StorageManager protocol, so even if we change the database, we will not need to change the entire implementation code:
**class User: Storable { var name: String
init(name: String) {
self.name = name
}
}**
**class UserService: StorageManager { func save(object: Storable) { // Saves user to database } }**
Swift :
Dependency Inversion Principle Example 2
Here’s another principle with a scary name. This principle says that high level things in your app, like your view controller, should not depend directly on low level things, like a networking component. Instead, it should depend on an abstraction of that component. In practice, this means that the view controller should not use a Fetcher class, but a Fetchable protocol.
The reason is to reduce coupling. Strong coupling occurs when your class depends heavily on the implementation of another class. It might be calling a lot of methods, making assumptions about the inner working of that class, or use variable names that tie it to that specific class.
Strong coupling is bad because it makes it harder to change the code base. If you’re using a CoreDataService, and suddenly want to switch to a RealmService, you best hope your view controller doesn’t rely heavily on the former.
The way to go around this issue is to use a DatabaseService protocol, that the CoreDataService will implement.
protocol DatabaseService {
func getUsers()-> [User]
}
class CoreDataService: DatabaseService {
// …
}
In your view controller, you pretend that the class doesn’t exist, and just use an instance of that protocol.
let databaseService: DatabaseService = CoreDataService()
You do this because protocols are less specific than classes. A class has a specific name and specific methods you can use. On the other hand, protocols are, by definition, abstract. More than one class can implement a protocol, making them perfect for reducing coupling.
To change to Realm, all you need to do is make a new class that conforms to the same protocol. Because you didn’t rely on a specific implementation, you don’t need to change code in your view controller. It’s a huge time saver!
If you think about coupling from the beginning it will save your butt in the long run.