Interface Segregation Principle in Golang Using Dragon Ball Example | by Hernan Reyes | Nov, 2022 - Exotic Digital Access
  • Kangundo Road, Nairobi, Kenya
  • support@exoticdigitalaccess.co.ke
  • Opening Time : 07 AM - 10 PM
Interface Segregation Principle in Golang Using Dragon Ball Example | by Hernan Reyes | Nov, 2022

Interface Segregation Principle in Golang Using Dragon Ball Example | by Hernan Reyes | Nov, 2022

Learn the principle to remove unnecessary responsibilities from clients of interfaces in your code

Interface Segregation Principle in Golang Using Dragon Ball Example | by Hernan Reyes | Nov, 2022
Cover

When working with interfaces, you may find that you have an interface that is implemented by various clients, but we’re not all clients need to implement all the methods of the interface. This is bad because you’re forcing clients on to implement methods they don’t need, leaving empty methods like this:

func (c Client) MethodTheClientDontNeed() {  
panic("implement me")
}

The Interface Segregation is part of the SOLID principles, and what it says is that the clients of an interface must implement only the methods that they need, or else you must split your interface into more specific ones, so the clients only implement the methods that they need.

Interface Segregation Principle in Golang Using Dragon Ball Example | by Hernan Reyes | Nov, 2022
Interface Segregation Principle

To introduce you to this principle, I’ll be using Dragon Ball as a reference, so let’s imagine that we’re working on a new Dragon Ball game, so what we do is create a Warrior interface that’ll be implemented by all characters of the anime, for this example It’ll be Mr. Satan and Goku:

Interface Segregation Principle in Golang Using Dragon Ball Example | by Hernan Reyes | Nov, 2022
Warrior interface

As we see, both Mr. Satan and Goku, implement the Warriorinterface, but if you’ve watched the anime, you know that Goku can implement the three methods, but Mr. Satan doesn’t because he can’t Transform.

So in the example, he’ll be implementing a method that he doesn’t need — in this case, the method will be empty.

To avoid that, we’ll make use of the segregation principle, so we’ll create a Super Saiyaninterface that will have the Transformmethod that is only implemented by Goku, so we’ll end with something like this:

Interface Segregation Principle in Golang Using Dragon Ball Example | by Hernan Reyes | Nov, 2022

Now, Mr. Satan, just implements the methods that he needs, thanks to the new interface Super Saiyanthat we created that is only implemented by Goku.

And as you can see, at the end will have at least one interface that will be implemented by all clients, this will be the interface will use as a type to refer to our characters.

Now let’s take those references into Golang and see what the code will look like. But before that, let’s look at how it would be without the principle:

Define the Interface Warrior :

package main

type Warrior interface {
Kick()
Punch()
Transform()
}

type Warriors []Warrior

func executeWithoutISP(warriors Warriors) {
for _, warrior := range warriors {
warrior.Kick()
warrior.Punch()
warrior.Transform()
}
}

Add the clients that will implement the Warrior interface:

package main

type MRSatan struct{}

func NewMRSatan() *MRSatan {
return &MRSatan{}
}

func (m MRSatan) Kick() {
println("MRSatan kicks")
}

func (m MRSatan) Punch() {
println("MRSatan punches")
}

// The empty method that we want to avoid
func (m MRSatan) Transform() {
// do nothing
}

package main

type Goku struct{}

func NewGoku() *Goku {
return &Goku{}
}

func (g Goku) Kick() {
println("Goku kicks")
}

func (g Goku) Punch() {
println("Goku punches")
}

func (g Goku) Transform() {
println("Goku transforms into a Super Saiyan")
}

We execute the abilities of each client:

package main

func main() {
var warriors = Warriors{}
warriors = append(warriors, NewMRSatan())
warriors = append(warriors, NewGoku())

executeWithoutISP(warriors)
}

When we run the program, we get the following output:

Interface Segregation Principle in Golang Using Dragon Ball Example | by Hernan Reyes | Nov, 2022
https://asciinema.org/a/536786

Everything works well, MR. Satan kicks and punches and Goku kicks, punches, and transforms, but the underlying code is not as good as it could be because MR. Satan client is implementing a method he doesn’t need:

// The empty method that we want to avoid
func (m MRSatan) Transform() {
// do nothing
}

Let’s solve this by applying the Interface Segregation principle:

Now instead of only having one interface, we created the SuperSaiyan one, so it can only be implemented by Goku :

package main

type Warrior interface {
Kick()
Punch()
}

type SuperSaiyan interface {
Transform()
}

type Warriors []Warrior

func executeWithISP(warriors Warriors) {
for _, warrior := range warriors {
warrior.Kick()
warrior.Punch()

// For each Warrior, we check if it is a SuperSaiyan
if superSaiyan, ok := warrior.(SuperSaiyan); ok {
superSaiyan.Transform()
}
}
}

Now, our MR. Satan client will only implement the Kick and Pucnhmethods:

package main

type MRSatan struct{}

func NewMRSatan() *MRSatan {
return &MRSatan{}
}

func (m MRSatan) Kick() {
println("MRSatan kicks")
}

func (m MRSatan) Punch() {
println("MRSatan punches")
}

And our Goku client will still implement the three methods:

package main

type Goku struct{}

func NewGoku() *Goku {
return &Goku{}
}

func (g Goku) Kick() {
println("Goku kicks")
}

func (g Goku) Punch() {
println("Goku punches")
}

func (g Goku) Transform() {
println("Goku transforms into a Super Saiyan")
}

Our main file remains the same as before:

func main() {
var warriors = Warriors{}
warriors = append(warriors, NewMRSatan())
warriors = append(warriors, NewGoku())

executeWithISP(warriors)
}

And if we run the program we still get the same response, but with a better underlying code:

Interface Segregation Principle in Golang Using Dragon Ball Example | by Hernan Reyes | Nov, 2022
https://asciinema.org/a/536787

ISP is a simple principle that helps you remove unnecessary responsibilities from your clients when implementing large interfaces used by various clients, but that can drive you to have countless interfaces depending on how big is the main interface you’re splitting so careful with that, in my experience I’ve just separated interfaces with at most 10 methods, ending with at most 3 interfaces which have worked well for me.

  1. Dive into Design Patterns
  2. Asciinema to record my terminal
  3. Excalidraw and Figma for the illustrations
  4. Repository

Source link

Leave a Reply