Object oriented in Go – the interface

As we saw in Chapter 1, Building Big with Go, the interface construct is key to polymorphism in Go—abstracting out details from a set of related objects so that we can simplify code. The interface defines a contract that can be assumed by clients, without knowledge (and thus coupling) of the actual class that implements the interface. Interfaces are types that declare sets of methods. Similar to interfaces in other languages, they have no implementation. Interfaces are at the core of Go's object-oriented support.

Many object-oriented languages define explicit implementations of an interface; however, Go is different. Here, the implementation is implicit through duck typing (as we saw in Chapter 1, Building Big with Go). Objects that implement all the interface methods automatically implement the interface. There is no inheritance or subclassing or implements keyword.

Duck typing is found in other languages such as Python, but one advantage of Go is the ability of the compiler to catch obvious mistakes, such as passing an int where a string was expected or calling with the wrong number of arguments.

To use interfaces, first define the interface type like so:

type LatLong stuct {
   Lat float64
   Long float64
  }
type Animal interface { GetLocation() LatLong SetLocation(LatLong) CanFly() bool Speak() }

In Golang, all implementations are implicit. If the method set of a type T is a super set of the method set declared by an interface type I, then the type T implements the interface type I, implicitly. Here, T can also be an interface type. If T is a named non-interface type, then *T must also implement I, for the method set of *T is a super set of the method set of T.

So, for example, for the animal interface, we can define Lion and Pigeon classes that both implement the interface, as follows:

// The Lion Family
//
type Lion struct {
   name       string
   maneLength int
   location   LatLong
}

func (lion *Lion) GetLocation() LatLong { return lion.location }
func (lion *Lion) SetLocation(loc LatLong) { lion.location = loc }
func (lion *Lion) CanFly() bool { return false }
func (lion *Lion) Speak() string { return "roar" }
func (lion *Lion) GetManeLength() int { return lion.maneLength }
func (lion *Lion) GetName() string { return lion.name }

Here is the code for the Pigeon class:

// The Pigeon Family
//
type Pigeon struct {
   name     string
   location LatLong
}
func (p *Pigeon) GetLocation() LatLong { return p.location }
func (p *Pigeon) SetLocation(loc LatLong) { p.location = loc }
func (p *Pigeon) CanFly() bool { return false }
func (p *Pigeon) Speak() string { return "hoot" }
func (p *Pigeon) GetName() string { return p.name }

The whole point of this is, of course, polymorphism—Lion and Pigeon can be used in any place where the animal interface is expected.

As described in Chapter 1, Building Big with Go, this is also called duck typing—"If it quacks like a duck, it must be a duck." Specifically, if a type T implements an interface type I, then values of T can be assigned to values of I. Calling a method of an interface value will call the corresponding method of the dynamic value of the interface value. The polymorphism is demonstrated by this code:

// The symphony
func makeThemSing(animals []Animal) {
    for _, animal := range animals {
    fmt.Println(animal.GetName() + " says " + animal.Speak())
  }
}
func main() { var myZoo []Animal
Leo := Lion{ "Leo", 10, LatLong{10.40, 11.5}, }
myZoo = append(myZoo, &Leo)
Tweety := Pigeon{ "Tweety", LatLong{10.40, 11.5}, } myZoo = append(myZoo, &Tweety)
makeThemSing(myZoo) // do some work with the collection
}

A note on the implementation: polymorphism is typically implemented as either of the following:

  • Tables for all the method calls prepared statically (as in C++ and Java)
  • A method lookup at each call (JavaScript and Python)

Go has slightly different method tables but computes them at runtime. Essentially, interfaces are represented as a pointer-pair: one pointer to information about the type and method tables (called i-table), and the other pointer references the associated data. For example, take a look at the following assignment:

var aAnimal Animal
aAnimal =  &Lion{
   "Leo",
   10,
   LatLong{10.40, 11.5},
 }

It can be pictorially visualized as follows:

This diagram can help us visualize how the language runtime implements interfaces and polymorphism.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset