Go is  not a fully featured object-oriented language. Instead, it offers a subset of OOP concepts. For example, you can attach a method to any type or encapsulate data through the use of internal or public (exported) identifier.

In the following exemple, Person has both a public and private fields and a method attached.

type Person struct {
   ID string // public as it starts uppercase
   name string // private
}

func (p Person) sayHello(){
   fmt.Printf("Hello %s", p.name)
}

func main(){
   p := Person{ID: "0", name: "John"}
   p.sayHello()
}

So we’ve got both encapsulation and methods covered. One often used feature of OOP, well someone could even say it defined object for a lot of people, is the capacity to extend a class through inheritance.

What if you want to do something akin inheritance in Go ?

Back to basics : why using inheritance ?

The main motivation to use inheritance boils down to 2 use cases:

  • Code reuse: you have a bunch of code you want to reuse between related classes
  • Interface: you want the child class to share a common interface as parent and be interacted with as parent class ( Liskov substitution principle)

Let’s say it quickly: reusing code is generally not a good motivation for using inheritance.

Reusability is better served with composition. In general,** you should favor composition over inheritance, **especially if it’s only for sharing code.

Let’s take a very basic example written in Java. We want to make a hash map that encrypts objects added to it. As we surely want to reuse the hashSet code of the standard library, we write the following:

class EncryptedHashSet extends HashSet {
  private Object encrypt(Object o){
    // do some fancy encryption
    return o;
  }

  public boolean add(Object o) {
    return super.add(encrypt(o));
  }
}

It’s of course working but there are numerous drawbacks:

  • Very often it breaks encapsulation as the child class inherits a lot of things it has no use for and allow access to implementation details. It’s clearly the case here as you can access any inherited data and methods from the base class.
  • There is a lack of flexibility because you may want to encrypt other things with the same algorithm, so encryption code should not be so tied up to this class.
  • This code is difficult to test because the dependancy to HashSet is hard coded and cannot be changed easily. Outside of testing, it’s also a desirable feature as you may want to change behaviour at runtime (think  strategy pattern).

This example is better written using composition. Like this:

class ComposedEncryptedHashSet {
  private HashSet hashSet;
  private Encrypter encrypter;

  public ComposedEncryptedHashSet(HashSet hs, Encrypter encrypter) {
    this.hashSet = hs;
    this.encrypter = encrypter;
  }

  private Object encrypt(Object o){
    return encrypter.encrypt(o);
  }

  public boolean add(Object o){
    return hashSet.add(o);
  }
}

In this example, we:

  • Split responsibility by getting the encryption code outside of the class, this code may be reused by any other class.
  • We prevent accessing internal data and methods from the HashSet class.
  • The code is testable as composition allows for easy replacement with a mock implementation.

#oop #go #golang

What is the Extension Interface Pattern in Golang (Go)?
34.40 GEEK