Golang interfaces

From wikinotes
Revision as of 00:07, 31 July 2022 by Will (talk | contribs) (→‎Empty Interfaces)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Similar to other languages, interfaces in go define a contract of method-signatures that implementors must have.
Unlike other languages, golang interfaces are implicit -- an object with all of the required methods automatically satisfies an interface.
In go, it is encouraged to keep very small interfaces (ex. 1 method). The larger the interface, the weaker the abstraction.

Similar to other languages, a param can be typed to accept an interface, which abstracts the actual type that is received.

NOTE:

go interfaces define a contract of methods, but not struct fields.
you'll need to define getters/setters for fields

Basics

Libraries do not need to expose interfaces in go, you can create them for the subset of methods that are useful to you.

  • An interface is a promise that an object implements a set of method-signatures.
  • objects are automatically a part of an interface if they have methods, there is no implements Foo
  • Empty interfaces match all oject types in go. (ex fmt.Println() can accept any type)
// rectangle.go
type Rectangle struct {
    width int
    height int
}

func (rect Rectangle) Area() int {
    return rect.width * rect.height
}
// area_calculator.go
type AreaCalculator interface {
    Area() int
}
// main.go
func LargestArea(a, b AreaCalculator) AreaCalculator {
    if a.Area() > b.Area() {
        return a
    }
    return b
}

func main() {
    rect1 := Rectangle{5, 5}
    rect2 := Rectangle{2, 10}
    fmt.Println(LargestArea(rect1, rect2) == rect1)
}

Interfaces with Pointer Methods

When a method's self is a pointer, you must pass in a reference (&value) to the object in the method call.
Reusing the last example

// rectangle.go
type Rectangle struct {
    width int
    height int
}

func (rect *Rectangle) Area() int {  // <-- SEEME 'rect *Rectangle' indicates this method is added to a reference to this object
    return rect.width * rect.height
}
// area_calculator.go -- IDENTICAL
type AreaCalculator interface {
    Area() int
}
// main.go
func LargestArea(a, b AreaCalculator) AreaCalculator {
    if a.Area() > b.Area() {
        return a
    }
    return b
}

func main() {
    rect1 := Rectangle{5, 5}
    rect2 := Rectangle{2, 10}
    fmt.Println(rect1.Area())
    fmt.Println(rect2.Area())
    fmt.Println(LargestArea(&rect1, &rect2))  // <--- SEEME passing in reference!!
}

Embedding in Interfaces

Embedding an interface within an interfaces includes it's method-signatures in the parent.

type Walker interface {
    Walk nil
}

type BubblegumChewer interface {
    ChewGum nil
}

// includes all methods from previous two interfaces
type BubblegumChewingWalker interface {
    Walker
    BubblegumChewer
}

Embedding in Structs (inherit methods)

By embedding interfaces in a struct, you inherit the methods from the wrapped object.

package main

import "fmt"

// interface
type Greeter interface {
	Greet() string
}

// English/French implementations of Greeter
type EnglishGreeter struct{}
func (this *EnglishGreeter) Greet() string {
	return "hello"
}

type FrenchGreeter struct{}
func (this *FrenchGreeter) Greet() string {
	return "bonjours"
}

// Inherits Greet(), but can define it's own fields/methods
type GreeterWrapper struct {
	Greeter
}
func (this *GreeterWrapper) GreetByName(name string) string {
	return fmt.Sprintf("%s, %s", this.Greet(), name)
}

func main() {
	english := EnglishGreeter{}
	englishWrapper := GreeterWrapper{Greeter: &english}
	fmt.Println(englishWrapper.Greet())
	fmt.Println(englishWrapper.GreetByName("will"))

	french := FrenchGreeter{}
	frenchWrapper := GreeterWrapper{Greeter: &french}
	fmt.Println(frenchWrapper.Greet())
	fmt.Println(frenchWrapper.GreetByName("will"))

}

// hello
// hello, will
// bonjours
// bonjours, will

Empty Interfaces

Empty interfaces will accept any type within go.
It is used for example by fmt.Println() so that it can accept any type of parameter.

func typeInfo(obj interface{}) string {
    fmt.Printf("(%v, %T)\n", obj, obj)
}

Cast as Concretion

See golang variables for more details

var foo MyInterface
foo = MyConcretion{}
castFoo := foo.(MyConcretion)