SOLID explained with iOS examples
Tamas Dancsi
Posted on June 20, 2024
The SOLID principles are a set of guidelines for designing software that is easy to maintain and extend.
1. Single Responsibility Principle (SRP)
A class should have only one reason to change, meaning it should only have one job or responsibility.
// WRONG: A class that handles both user authentication and user data storage
class UserManager {
func authenticateUser(username: String, password: String) -> Bool {
// Authentication logic
return true
}
func saveUserDetails(user: User) {
// Save user details logic
}
}
// CORRECT: Separate classes for authentication and data storage
class Authenticator {
func authenticateUser(username: String, password: String) -> Bool {
// Authentication logic
return true
}
}
class UserStorage {
func saveUserDetails(user: User) {
// Save user details logic
}
}
2. Open/Closed Principle (OCP)
Software entities should be open for extension but closed for modification.
// WRONG: A class that needs to be modified to support new types of notifications
class NotificationManager {
func sendNotification(type: String) {
if type == "email" {
// Send email notification
} else if type == "sms" {
// Send SMS notification
}
}
}
// CORRECT: Extendable notification types using protocol
protocol Notification {
func send()
}
class EmailNotification: Notification {
func send() {
// Send email notification
}
}
class SMSNotification: Notification {
func send() {
// Send SMS notification
}
}
class NotificationManager {
func sendNotification(notification: Notification) {
notification.send()
}
}
3. Liskov Substitution Principle (LSP)
Subtypes must be substitutable for their base types without altering the correctness of the program.
// WRONG: A subclass that breaks the functionality of the superclass
class Bird {
func fly() {
print("Flying")
}
}
class Ostrich: Bird {
override func fly() {
// Ostrich can't fly, so this method should not exist here
fatalError("Ostriches can't fly!")
}
}
// CORRECT: Using protocol to define a contract for flying birds
protocol Flyable {
func fly()
}
class Sparrow: Flyable {
func fly() {
print("Flying")
}
}
class Ostrich {
// Ostrich does not conform to Flyable because it can't fly
}
4. Interface Segregation Principle (ISP)
Clients should not be forced to depend on interfaces they do not use.
// WRONG: A single interface with too many responsibilities
protocol Worker {
func work()
func eat()
}
class Developer: Worker {
func work() {
// Coding
}
func eat() {
// Eating lunch
}
}
class Robot: Worker {
func work() {
// Coding
}
func eat() {
// Robots don't eat, so this method is not applicable
}
}
// CORRECT: Separate interfaces for different responsibilities
protocol Workable {
func work()
}
protocol Eatable {
func eat()
}
class Developer: Workable, Eatable {
func work() {
// Coding
}
func eat() {
// Eating lunch
}
}
class Robot: Workable {
func work() {
// Coding
}
}
5. Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions.
// WRONG: High-level module depends on low-level module
class Database {
func save() {
// Save to database
}
}
class UserRepository {
private let database = Database()
func saveUser() {
database.save()
}
}
// CORRECT: High-level module depends on an abstraction
protocol Storage {
func save()
}
class Database: Storage {
func save() {
// Save to database
}
}
class UserRepository {
private let storage: Storage
init(storage: Storage) {
self.storage = storage
}
func saveUser() {
storage.save()
}
}
💖 💪 🙅 🚩
Tamas Dancsi
Posted on June 20, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.