Today, let’s revise the I of the SOLID principles: Interface Segregation. The name sounds a little bit confusing, but it’s an easy principle to grasp and the article will be quite short. It states that “A client should not be forced to implement an interface that it doesn’t use.”
However, what this definition exactly means? As you probably find with all these principles, it comes down to the knowledge that one object has of another object. Let’s illustrate this principle with some code that is hopefully easily understandable.
Let’s imagine we have a Worker
struct with two methods and a Captain
struct that has a manage(worker: Worker)
method:
struct Worker {
func work() {
"I'm working"
}
func sleep() {
"Ok, a bit tired, let me have some rest 🥱"
}
}
struct Captain {
func manage(worker: Worker) {
worker.work()
worker.sleep()
}
}
So at this point, everything looks good, right. We have our captain that can manage a worker. But what about the captain recruits a Robot to do the same job. Well, our program is going to change a little bit. You might think to do something like this, for instance:
protocol Worker {
func work()
func sleep()
}
struct Human: Worker {
func work() {
"I'm working"
}
func sleep() {
"Ok, a bit tired, let me have some rest 🥱"
}
}
struct Robot: Worker {
func work() {
"🤖 working"
}
func sleep() {
}
}
struct Captain {
func manage(worker: Worker) {
worker.work()
worker.sleep()
}
}
We set up a Worker
protocol, and because a worker
should work and sleep, we put those methods in the protocol. Then we changed our Worker
struct by a Human
struct and implemented the Worker
protocol. A human can work and sleep, so we have no problem putting logic on those two methods.
Now we have a Robot
struct implementing the same Worker
protocol. Yes, a robot can definitely work, so it’s totally fine; it knows how to do it, but what about the sleep
method? Robots don’t sleep so we cannot implement this method. And this is what the Interface Segregation is all about: clients should never implement interfaces that they don’t use and, right now, the Robot
struct is being forced to implement the sleep
method. You might say, ok, if it doesn’t use it, so simply delete the sleep
method then. Easy said than done. Because we’re implementing the Worker
protocol, we’ll have a compiler error saying Type 'Robot' does not conform to protocol 'Worker'
, so we have to implement it even though its functionality is not required.

Doing things right
So how to fix this. The first solution is to break down the Worker
protocol into smaller chunks. Remember, a protocol with only a single method is perfectly fine; the thing we should be worried about, in fact is the opposite, a protocol containing too many methods because it is susceptible to break the Single Responsibility Principle (all these principles are linked somehow).
Instead let’s have two protocols, a Workable
and Sleepable
protocols with the respective work
and sleep
methods.
protocol Workable {
func work()
}
protocol Sleepable {
func sleep()
}
Now let’s implement those protocols on our structs. For the Human
struct, we’ll have something like this:
struct Human: Workable, Sleepable {
func work() {
"I'm working"
}
func sleep() {
"Ok, a bit tired, let me have some rest 🥱"
}
}
And for the Robot
struct, since it cannot sleep, we’ll just implement the Workable
protocol:
struct Robot: Workable {
func work() {
"🤖 working"
}
}
So now that the Robot
struct is no longer conforming to the Working
protocol, it is not being forced to implement a method it doesn’t use; therefore we remove the sleep
method, and we are now conforming to the Interface Segregation Principle.
Let’s come back to our Captain
struct now.
struct Captain {
func manage(worker: Worker) {
worker.work()
worker.sleep()
}
}
So before, we had a dependency on Worker
. Now we say it wants something that conforms to Workable
protocol.
struct Captain {
func manage(worker: Workable) {
worker.work()
}
}
However, let’s make a quick call back to the Open-Close Principle and decide we want to sleep in the manage method as well. We might be tempted to do something like this:
struct Captain {
func manage(worker: Workable) {
if worker is Human {
let human = worker as! Human
human.work()
human.sleep()
} else {
worker.work()
}
}
}
And if you remember, as we discussed in earlier articles, doing type checks like this should break the OCP. Instead, why do we not create another protocol called Manageable
with a single method?
protocol Manageable {
func beManaged()
}
Now we go back to our Captain
struct and say the manage
method accepts a worker of type Manageable
.
struct Captain {
func manage(worker: Manageable) {
worker.beManaged()
}
}
What left to be done is to implement the Manageable
to the Human
and Robot
structs:
struct Human: Workable, Sleepable, Manageable {
func work() {
"I'm working"
}
func sleep() {
"Ok, a bit tired, let me have some rest 🥱"
}
func beManaged() {
self.work()
self.sleep()
}
}
struct Robot: Workable, Manageable {
func work() {
"🤖 working"
}
func beManaged() {
self.work()
}
}
struct Captain {
func manage(worker: Manageable) {
worker.beManaged()
}
}
Now because the manage
method does not depend upon a worker object itself, we have improved the design and reduced coupling or, in another words, now our client, which is the manage
method, can use any object that conforms to the Manageable
protocol without needing to depend upon any particular implementation of worker.
Final Thought:
As I told you, this principle is pretty easy to grasp. The main thing to remember is to not implement a contract that won't be used on clients. For our final article about the SOLID principle, we'll see the Dependency Inversion Principle. Until then, have a nice week 👊