MVVM example with applied SOLID principles in Swift



Applying SOLID principles (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) in the context of MVVM architecture is essential for creating maintainable, scalable, and flexible code. Here's an example of MVVM in Swift with the application of SOLID principles:


1. Single Responsibility Principle (SRP):

Each class should have a single responsibility. In MVVM, the ViewModel should handle business logic, while the View should manage UI.

2. Open/Closed Principle (OCP):

The code should be open for extension but closed for modification. We should be able to add new functionality without altering existing code.

3. Liskov Substitution Principle (LSP):

Objects of a derived class should be able to replace objects of the base class without affecting the correctness of the program.

4. Interface Segregation Principle (ISP):

Clients should not be forced to implement interfaces they don't use. It's better to have many small, specific interfaces than one large, general-purpose interface.

5. Dependency Inversion Principle (DIP):

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.

Here's an example: 

import Foundation

// MARK: - Model
struct Task {
    let id: Int
    var title: String
    var isCompleted: Bool
}

// MARK: - ViewModel
protocol TaskViewModelProtocol {
    var numberOfTasks: Int { get }
    func taskAtIndex(_ index: Int) -> Task
    func toggleTaskCompleted(_ index: Int)
}

class TaskViewModel: TaskViewModelProtocol {
    private var tasks: [Task] = []
    
    var updateHandler: (() -> Void)?
    
    init() {
        // Initialize some sample tasks.
        self.tasks = [
            Task(id: 1, title: "Buy groceries", isCompleted: false),
            Task(id: 2, title: "Walk the dog", isCompleted: true),
            Task(id: 3, title: "Write MVVM example", isCompleted: false),
        ]
    }
    
    var numberOfTasks: Int {
        return tasks.count
    }
    
    func taskAtIndex(_ index: Int) -> Task {
        return tasks[index]
    }
    
    func toggleTaskCompleted(_ index: Int) {
        tasks[index].isCompleted.toggle()
        updateHandler?()
    }
}

// MARK: - View
class TaskListView {
    private let viewModel: TaskViewModelProtocol
    
    init(viewModel: TaskViewModelProtocol) {
        self.viewModel = viewModel
    }
    
    func displayTasks() {
        for index in 0..<viewModel.numberOfTasks {
            let task = viewModel.taskAtIndex(index)
            print("Task \(task.id): \(task.title) - Completed: \(task.isCompleted)")
        }
    }
}

// MARK: - Dependency Injection
let viewModel = TaskViewModel()
let view = TaskListView(viewModel: viewModel)
viewModel.updateHandler = {
    view.displayTasks()
}

// Trigger an update to demonstrate the data flow
viewModel.toggleTaskCompleted(0)

In this example:

TaskViewModel follows SRP by handling business logic.

TaskListView follows SRP by handling UI-related tasks.

We use a protocol TaskViewModelProtocol to adhere to the LSP and DIP, allowing easy substitution and dependency injection.

Dependency injection is applied to decouple dependencies and make the system more testable and maintainable.

This example demonstrates MVVM with applied SOLID principles, promoting separation of concerns, testability, and flexibility in the codebase.

Post a Comment

0 Comments