What Is the Extension Interface Pattern in Go?
By: Emmanuel Sys
Tags: Software, Oop, Programming, Development, Go

A recent video by Russ Cox and Rob Pike officially coined the term “Extension interface pattern” in Go. What are we talking about?

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. But one often used feature of OOP is the capacity to extend a class through inheritance. In fact, you could even say it defined object programming for a lot of people.

What if you want to do something like 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(encrypt(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.

Back to Go : how to inherit something?

First, let’s make this clear: there is no such thing as inheritance in Go.

Instead, Go makes use of interface. You don’t have to explicitly implement an interface in Go as the language use “Duck typing” (well, in fact it’s called “structural typing” because it happens at compile time) : If it looks like a duck, and it quacks like a duck, it’s a duck.

One such example is the Stringer interface to convert any type to a string. If you define your own type, you only need to implement the String() method and you are effectively implementing Stringer, no need for an explicit declaration like in Java. This interface is used when you print any type like in the example below.

type Person struct {

ID string

name string

}



func (p Person) sayHello(){

fmt.Printf("Hello %s", p.name)

} // this method makes Person implements Stringer interface

func (p Person) String() string {

return fmt.Sprintf("Person called %s", p.name)

}



func main(){

p := Person{ID: "0", name: "John"}

fmt.Printf("Let's print: %s", p)

} // running should display "Let's print: Person called John"

As we’ve seen before, a legitimate inheritance use-case is when you want to have a parent interface over a bunch of related types. So Go got you covered on this.

If you want to reuse code, like we’ve discussed before, you should use composition. As inheritance does not exist in Go, there’s no debate of composition superiority here 😉

So how to use composition in Go?

Go has a very clean way to compose things. In this example, we have a Response struct with common properties like an ID or type. In real life, it typically represents a set of related REST service answers.

You want to handle a response with these common properties but also additional specific data. Of course, you want to keep DRY and reuse code. Do it like so:

type Response struct {

ID string

Type string

}



type SpecificResponse struct {

Response // Response type is embedded inside SpecificResponse

Data string

} func main(){

var sr SpecificResponse

fmt.Printf(sr.ID)

fmt.Printf(sr.Response.ID)

}

Note the syntax: you embed the type Response inside the SpecificResponse type. Fields of Response can then be accessed directly from the specific response sr.ID or using the embedded type sr.Response.ID .

But in Go, you can also apply this to interface. It’s not new but now has a somewhat official name: the Extension Interface pattern.

Let’s look at an example from the io/fs draft:

type File interface {

Stat() (os.FileInfo, error)

Read([]byte)(int, error)

Close() error

} type ReadDirFile interface {

File

ReadDir(n int) ([]os.FileInfo, error)

}

Here you can also use composition to extend an interface. You have a File interface with basic capabilities you then extend through composition to read directories. It makes it easy to split interfaces to avoid bloated interface with a lot of methods to implement (interface segregation principle)

Conclusion

The lack of inheritance in Go can be coped with by using interface and composition, which is supported with the special “embedded” syntax. The language even allows you to use the composition inside interfaces to limit the size of those.

As inheritance is commonly badly used, it generally won’t be missed and force you to produce a more flexible code.