SOLID Go: Interface Segregation Principle

4 minute read

Intro Interface Segregation Principle

The Interface Segregation Principle is the fourth of the five SOLID Software Design Principles. Whereas the first three principles relate to types, hierarchies and inheritance, the Interface Segregation Principle (ISP) advises on proper interface sizes. Some people claim the ISP is basically the same as the Single Response Principle, and they are not entirely wrong. Both principles emphasize the importance of very narrow, precise cohesiveness to the point that a single class, package or interface (also called building blocks) contains just enough code to serve a single purpose but nothing extra. While the Single Responsibility Principle (SRP) cares about every building block in your design, the ISP has the adopting building blocks in mind, which will implement a given interface. What is the background and context of this statement?

Interface Segregation Principle in General

Codebases which grew over time often lack proper software designs. The design “just somehow evolves” over time. When software developers leave the Single Responsibility Principle out of sight, they may create interfaces that force concrete structs to contain more code than necessary. An interface that feels well-designed in terms of the Single Responsibility Principle still might violate the Interface Segregation Principle.

Let’s say there is a MouseInteractionObserver interface in your codebase that contains methods the following methods:

1type MouseInteractionObserver interface {
2	OnMouseUp(event MouseInteractionEvent)
3	OnMouseDown(event MouseInteractionEvent)
4	OnMouseHover(event MouseInteractionEvent)
5}

The listed interface could be considered designed correctly in the context of the Single Responsibility Principle Principle because the interface contains just “jobs” about mouse interactions. However, this is where the Interface Segregation Principle comes into play. Because usually, when a building block, like a class in Java or a struct in Golang, adopts an interface, the building block has to implement all defined signatures of the interface, or your code will not compile.

With this background knowledge in mind, please imagine you want to receive the mouse down and mouse up events but don’t care about the mouse hover event. You would still be forced to implement all the methods. An interface that poorly adheres to the Interface Segregation Principle will still force you to at least implement an empty OnMouseHover function in your concrete building block. Implementing empty behavioural functions for no reason is considered lousy code. But there is even more to it. Interfaces with too many method requirements might lead to:

  1. violating the SRP in concrete building blocks adopting this interface because you and your colleagues will feel tempted to add the extra responsibility into the already (empty) implemented method. Because why not use this extra space when it is already there?
  2. a higher amount of incoming couplings per building block. Because when this building block implements the interface with many function requirements, others might access these methods at some point.

Adopting multiple Interfaces per Struct in Golang

In the previous section of this article, you learned about at least three good reasons to keep your interface definitions close to the Interface Segregation Principle (ISP). However, if you keep your interface design close to the ISP, you will likely end up with multiple interface definitions instead of just one. And that’s OK! Because similar to most object-oriented programming languages, also Golang allows a struct to implement multiple interfaces at the same time.

 1package main
 2
 3import "fmt"
 4
 5type A interface {
 6	Hello()
 7}
 8
 9type B interface {
10	World()
11}
12
13type C struct {
14}
15
16func (c C) Hello() {
17	fmt.Println("Hello")
18}
19
20func (c C) World() {
21	fmt.Println("World")
22}
23
24func main() {
25	cObj := C{}
26	var aObj A = cObj
27	aObj.Hello()
28	var bObj B = cObj
29	bObj.World()
30}

This way, you will be able to split already well-defined interfaces, due to the Single Responsibility Principle, into even more well-defined partial interfaces in respect to the Interface Segregation Principle. However, with the typical challenges that come with all kinds of abstractions like those introduced by interfaces, you must act carefully about unwanted side effects. To learn how to avoid these, consider the SOLID Go articles on the Open Closed Principle and the Liskov Substitution Principle.

Conculsion

Since Golang makes heavy use of interfaces, so the Interface Segregation Principle (ISP) applies very much during your everyday Golang programming. The ISP and the SRP (Single Responsibility Principle) share the common notion that “the smaller the job scope of a building block, the better”. However, the ISP extends this statement and has the harmful effects of a poorly designed interface on its consumer in mind. Therefore, applying the Single Responsibility Principle in Golang will also lead to structs that implicitly implement multiple interfaces simultaneously instead of just a single interface with many empty functions.