Embracing Single Responsibility Principle in iOS

Embracing Single Responsibility Principle in iOS

How to embrace Single Responsibility Principle (SRP) for Scalable Architectures

One of the foundational principles of clean code and scalable software design is the Single Responsibility Principle (SRP)—a class should have one, and only one, reason to change. While it sounds straightforward, it often gets overlooked, especially when striving for simplicity in smaller projects. But as applications scale, tight coupling can become a bottleneck for maintainability and testability. Let me illustrate this with an example.

Example of tightly Coupled objects where ViewModel is confirming to protocol A

protocol A {
    func abc()
}

extension A {
    func abc() {
        print("Hi")
    }
}

class ViewModel: ObservableObject, A {
    init() {
        self.abc()
    }
}

At first glance, this implementation seems simple and effective. The ViewModel directly implements the functionality of protocol A. However, it introduces tight coupling, making it harder to:

1. Substitute different implementations of A during testing or future requirements.

2. Mock or stub dependencies for unit tests.

3. Scale and extend functionality without changing the ViewModel.

Solution: Dependency Injection

By decoupling the dependency through Dependency Injection, we adhere to SRP, ensuring the ViewModel is only responsible for managing the logic, not creating or maintaining the dependencies:

class ViewModel: ObservableObject {
    var a: A

    init(a: A) {
        self.a = a
        self.a.abc()
    }
}

Now, the ViewModel doesn’t care how A is implemented. Whether it’s a mock implementation for unit tests or a fully-fledged service class for production, it simply uses the injected dependency.

Why is this Better?

1. Testability: You can inject mock implementations of A to test the ViewModel without relying on the real service.

2. Scalability: When requirements change, you can introduce new implementations of A without modifying the ViewModel.

3. Readability: Each component has a clear, focused responsibility, improving the overall structure.

Final Thoughts

Applying SRP and embracing Dependency Injection may feel like over-engineering for small projects, but as your application grows, you’ll thank yourself for these early decisions. Clean, modular code not only reduces maintenance costs but also accelerates onboarding for new team members and speeds up testing cycles.