SOLID is a coding principle and we can write flexible, dynamic, testable and effective codes with SOLID principles.

SOLID has five principle. They are
- Single responsibility
- Open-closed
- Liskov substitution
- Interface segregation
- Dependency inversion
Let’s examine these in detail.
1. Single Responsibility Principle
If our classes or functions are handling multiple responsibilities, we can say that this is mistake. Because each class or function should have single responsibility.
There is a CategoriesVC class in this example. And there are codes to setup the view model and navigation title in viewDidLoad function. But these codes have different responsibilities.
final class CategoriesVC: UIViewController {
//MARK: - PROPERTIES
var viewModel: CategoriesVMProtocol? {
didSet {
viewModel?.delegate = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
viewModel?.fetchCategories()
navigationItem.title = "Categories"
}
}
I created setupViewModel and setupNavigationBar functions in this example. Because these functions have different responsibilities and each function have own responsibility in this example.
final class CategoriesVC: UIViewController {
//MARK: - PROPERTIES
var viewModel: CategoriesVMProtocol? {
didSet {
viewModel?.delegate = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
setupViewModel()
setNavigationTitle()
}
//MARK: - PRIVATE FUNCTIONS
private func setupViewModel(){
viewModel?.fetchCategories()
}
private func setNavigationTitle() {
navigationItem.title = "Categories"
}
}
2.Open-Closed Principle
In this principle, every class and structure should closed for modification and open for extension. For instance we want to create a class to calculate the area of any shape. In this case we have to create a parent class named Shape
I created rectangles and triangles array in AreaCalculator class. I had to create two variable this is because they have different types. And I created two for loop to sum to areas of shapes.
final class Rectangle {
let width: Double
let height: Double
init(width: Double, height: Double) {
self.width = width
self.height = height
}
func calculateArea() -> Double {
return width * height
}
}
final class Triangle {
let base: Double
let height: Double
init(base: Double, height: Double) {
self.base = base
self.height = height
}
func calculateArea() -> Double {
return (base * height) / 2.0
}
}
final class AreaCalculator {
let rectangles: [Rectangle] = [
Rectangle(width: 5, height: 3),
Rectangle(width: 7, height: 4)
]
let triangles: [Triangle] = [
Triangle(base: 5, height: 3),
Triangle(base: 7, height: 4)
]
func calculateTotalArea() -> Double {
var totalArea: Double = 0.0
for rectangle in rectangles {
totalArea += rectangle.calculateArea()
}
for triangle in triangles {
totalArea += triangle.calculateArea()
}
return totalArea
}
}
I created Shape protocol. Because rectangle objects and triangle objects are same type. I mean they are just a shape. So I gave inheritance to Rectangle and Triangle classes. This way I used just one array and one for loop to calculate the area of shapes.
protocol Shape {
func calculateArea() -> Double
}
final class Rectangle: Shape {
let width: Double
let height: Double
init(width: Double, height: Double) {
self.width = width
self.height = height
}
func calculateArea() -> Double {
return width * height
}
}
final class Triangle: Shape {
let base: Double
let height: Double
init(base: Double, height: Double) {
self.base = base
self.height = height
}
func calculateArea() -> Double {
return (base * height) / 2.0
}
}
final class AreaCalculator {
let shapes: [Shape] = [
Rectangle(width: 5, height: 3),
Triangle(base: 7, height: 4)
]
func calculateTotalArea() -> Double {
var totalArea: Double = 0.0
for shape in shapes {
totalArea += shape.calculateArea()
}
return totalArea
}
}
3. Liskov Substitution Principle
It is a principle that aims for classes work together in a meaningful way. So the parent classes should use to features of the child classes without change.
We have Dog and Fish classes in this example. This classes inherited from Animal class. But fish objects shouldn’t have run speed value.
class Animal {
let name: String
let runSpeed: Int
let swimSpeed: Int
init(name: String, runSpeed: Int, swimSpeed: Int) {
self.name = name
self.runSpeed = runSpeed
self.swimSpeed = swimSpeed
}
}
final class Dod: Animal {
}
final class Fish: Animal {
}
I created CanSwim and CanRun protocols. And I created Animals class to hold the name of animal. This is because every animal have a name but every animal can’t swim or can’t run. Now our parent classes can’t change the features of sub classes.
protocol CanSwim {
var swimSpeed: Int { get set }
}
protocol CanRun {
var runSpeed: Int { get set }
}
class Animal {
let name: String
init(name: String) {
self.name = name
}
}
final class Dod: Animal, CanRun {
var runSpeed: Int
init(name: String, runSpeed: Int) {
self.runSpeed = runSpeed
super.init(name: name)
}
}
final class Fish: Animal, CanSwim {
var swimSpeed: Int
init(name: String, swimSpeed: Int) {
self.swimSpeed = swimSpeed
super.init(name: name)
}
}
4. Interface Segregation Principle
This principle aims to separate the different features of interfaces into different interfaces. Because client shouldn’t depend on they don’t need.
In this example, the Animals protocol forces every animal to have both the eat and fly methods. However, the Dog class doesn't have the ability to fly, yet it is required to implement the fly method, leaving it empty. This violates the Interface Segregation Principle because each class should only have the functionality it needs.
protocol Animals {
func eat()
func fly()
}
class Bird: Animals {
func eat() {
print("I can eat")
}
func fly() {
print("I can fly")
}
}
class Dog: Animals {
func eat() {
print("I can eat")
}
func fly() {
print("I cannot fly")
}
}
In this example, specific protocols like Flyable and Feedable are used, allowing each animal class to only implement the features it requires. The Bird class implements both the fly and eat methods, while the Dog class only implements eat. This approach adheres to the Interface Segregation Principle because each class only contains the methods it needs. By doing so, we increase flexibility and keep the code cleaner and more maintainable.
protocol Flyable {
func fly()
}
protocol Feedable {
func eat()
}
class Bird: Flyable, Feedable {
func eat() {
print("I can eat")
}
func fly() {
print("I can fly")
}
}
class Dogs: Feedable {
func eat() {
print("I can eat")
}
}
5. Dependency Inversion Principle
This principle state that high-level classes shouldn’t depend on low-level classes. Also this principle abstract high-level classes and low-level classes using protocols. Therefore, the dependency between them becomes minimalized.
In this example, the UserManager class directly depends on the Networking class. Any changes in Networking would require modifications in UserManager as well. This violates the Dependency Inversion Principle because high-level modules like UserManager should not depend on low-level modules like Networking. This tight coupling makes the code harder to maintain and extend.
class Networking {
func fetchData() {
print("Fetching data from the server")
}
}
class UserManager {
let networking = Networking()
func fetchUserData() {
networking.fetchData()
}
}
let userManager = UserManager()
userManager.fetchUserData()
In this example, we use a NetworkingProtocol to decouple the UserManager and Networking classes. UserManager now depends on the protocol instead of the concrete Networking class. This makes the code more flexible and easier to maintain since changes in Networking won’t affect UserManager.
protocol NetworkingProtocol {
func fetchData()
}
class Networking: NetworkingProtocol {
func fetchData() {
print("Fetching data from the server")
}
}
class UserManager {
let networking: NetworkingProtocol
init(networking: NetworkingProtocol) {
self.networking = networking
}
func fetchUserData() {
networking.fetchData()
}
}
let networking = Networking()
let userManager = UserManager(networking: networking)
userManager.fetchUserData()
No comments:
Post a Comment
Note: only a member of this blog may post a comment.