Single Responsibility Principle for Class

You saw how the Dependency Inversion Principle, although lessor known, is the core guiding principle for a good clean architecture such as Clean Swift.

There’s a much more popular principle called the Single Responsibility Principle. You’ve likely heard of it. It’s easy to throw it around to say:

Make sure each of your methods does only one thing.

It sounds easy, simple, and logical. I mean, it makes sense, right?

But, in practice, how do you apply it to your app?

In this post, you’ll learn what the Single Responsibility Principle really is and why it is important and its benefits.

What is the Single Responsibility Principle?

The Single Responsibility Principle states that:

Each software module should have one and only one reason to change.

Or,

There should never be more than one reason for a class to change.

Let’s look at an example.

At first glance, all these methods represent functions of a car. So it makes sense to define them in the Car class.

But you can start to see how this can go:

It turns out a car can do a lot of things!

This Car class violates the Single Responsibility Principle. The responsibilities are:

  • A car can be driven.
  • A car should be maintained.
  • A car provides comfortable features.

Applying the Single Responsibility Principle to Car yields:

There is a problem. Whenever you want to create a car, does it mean you have to create three instances of DrivableCar, MaintainableCar, and ComfortableCar?

Protocol comes to your rescue.

However, this goes back to our original version. The Car class implements all the methods in these protocols.

Let’s consider how you use the Car class:

We’ve decoupled a car’s responsibilities into three separate interfaces. This prevents the ‘users’ of Car from mixing these responsibilities. For example, the compiler forbids you from calling drivableCar.playCD().

Why is it important?

If the Car class combines the driving, maintenance, and convenience functions in a single class, a change in one responsibility may alter the inner workings of the other two responsibilities. The responsibilities become coupled.

Coupled responsibilities lead to fragility in your classes. A class may maintain states that are used by multiple responsibilities. When a change is made to alter the driving function of the car, it may also affect the maintenance and convenience functions. These unintended side effects are undesirable and can break your app unexpectedly.

Let’s look at some code:

The battery is essential to multiple functions of a car. The accelerate(), addFuel(), and playCD() methods all need a battery. A change to any of these methods may break the other methods.

For example, a more expensive version of the car may have a smart actuator that, under low battery charge, prioritizes driving over convenience. The accelerate() method has priority over the playCD() method. Most of us can agree that it’s more important to get to where we want to go than to listen to music.

However, even with the use of protocols to separate the multiple functions, the Car class still implements all these functions in its implementation.

Delegation comes to your rescue.

The Car class only needs to conform to the Drivable, Maintainable, and Comfortable protocols. But it is free to implement these methods however it wishes.

One solution to maintain this separation of concern is for the Car class to delegate the driving, maintenance, and convenience functions of a car to other objects.

The Car class now delegates to the Driving, Maintenance, and Convenience classes. The implementation of the protocol methods simply invoke the corresponding methods of these new classes. Car can also maintain a single battery that can be passed around. This way, the state of the battery is always at a known state because it can only be altered through its exposed interface.

Wait, there’s more

Now that you’ve learned all about the Single Responsibility Principle at the class level. You can better design your classes in your app.

But there’s more.

If you have long, winding methods inside your classes, they can still do too much. They could take on multiple hidden responsibilities.

In a future post, we’ll look at the Single Responsibility Principle at the method level. Yes, you can apply the same principle to both classes and methods. As you’ll see, applying the Single Responsibility Principle at the method level can lead to much shorter methods.

Raymond
I've been developing in iOS since the iPhone debuted, jumped on Swift when it was announced. Writing well-tested apps with a clean architecture has been my goal.

7 Comments

  1. Let me understand something. Given what you mention here, could there be a possibility of one viewController having multiple interactors? (in the event the interactor is doing too much) OR, should there just be one Interactor with multiple workers?

    1. Hi Jay,

      You could have multiple interactors for one view controller, but I wouldn’t. I would instead use multiple workers if the business logic is complex.

    1. Hi Artem,

      It depends on the complexity of your class.

      In this example, delegating responsibilities to other objects is a better idea, because a racing game requires us to model all the internals of a car to provide realistic driving experience.

      But if you are modeling a game character and you just need to move him on a board with the same velocity, you can probably just group the related functions to animate the walking in an extension.

      Hope it helps.

  2. with the delegate example, how’s it possible to implement the following:

    ?

    The Convenience delegate needs battery information, which is e.g. in the Maintenance delegate.

Leave a Comment

Your email address will not be published. Required fields are marked *