The SOLID principles are foundational design guidelines in object-oriented programming, helping developers write clean, flexible, and maintainable code. Here’s a detailed look at each SOLID principle with practical Swift examples for iOS:
1. Single Responsibility Principle (SRP)
Definition:
A class should have only one reason to change, meaning it should only do one job.
class NetworkManager {
func authenticateUserApi(username: String, password: String) -> Bool {
// Verify user logic
return true
}
}
class RealmManager {
func storeUserDetails(user: User) {
// store user details logic
}
}
Now, each class has a single responsibility, making the code easier to modify, test, and extend.
2. Open/Closed Principle (OCP)
Definition:
Software entities should be open for extension, but closed for modification.
Example:
Suppose you want to calculate area for different shapes.
protocol Shape {
func area() -> Double
}
class Circle: Shape {
var radius: Double
init(radius: Double) {
self.radius = radius
}
func area() -> Double {
return .pi * radius * radius
}
}
class Rectangle: Shape {
var width: Double
var height: Double
init(width: Double, height: Double) { self.width = width; self.height = height }
func area() -> Double { return width * height }
}
class AreaCalculator {
func calculateArea(shape: Shape) -> Double {
return shape.area()
}
}
Now, to support new shapes, add new classes—no change needed in AreaCalculator.
3. Liskov Substitution Principle (LSP)
Definition:
Subclasses should be substitutable for their base class without affecting the correctness of the program.
protocol CanFlyer {
func fly()
}
class Bird: CanFlyer {
func fly() {
print("Sparrow can flye")
}
}
class Elephant {
/* Does not conform to CanFlyer */
}
Now, only birds that fly conform to CanFlyer, preventing misuse.
4. Interface Segregation Principle (ISP)
Definition:
Clients should not be forced to depend on interfaces they do not use.
protocol Printable {
func printDocument()
}
protocol Scannable {
func scanDocument()
}
class SimplePrinter: Printable {
func printDocument() { /*...*/ }
}
class AllInOnePrinter: Printable, Scannable {
func printDocument() { /*...*/ }
func scanDocument() { /*...*/ }
}
Printers implement only the protocols they actually need.
5. Dependency Inversion Principle (DIP)
Definition:
High-level modules should not depend on low-level modules. Both should depend on abstractions.
The Two Core Rules of Dependency Inversion
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
protocol Storage {
func save(string: String)
}
class FileManager: Storage {
func save(string: String) { /*...*/ }
}
class DatabaseManager: Storage {
func save(string: String) { /*...*/ }
}
class Handler {
let storage: Storage
init(storage: Storage) {
self.storage = storage
}
func handle(string: String) {
storage.save(string: string)
}
}
Handler now depends on an abstraction (Storage protocol), making it flexible and testable.
