Golang OOP, structs, and interfaces

Posted by Sam stoltenberg on December 11, 2022

This is a quick walkthrough of structs, interfaces, and some OOP concepts. Check out the go documentation for a more thorough walkthrough on:

Quick note: some error messages and prints may be different to a normal go execution as this is being done with jupyter notebook using gophernotes.

Structs

import "fmt"

type Vertex struct {
	X int
	Y int
}

fmt.Println(Vertex{1, 2})

nil
{1 2}

Struct method

Now if we want to create a method of a struct, we simply write a normal function with the struct as the receiver

func (v Vertex) GetX() int {
    
    // Note this
    v.X += 1

    return v.X
}

v := Vertex{1, 0}

v.GetX()
v.GetX()
v.GetX()
2

Notice how we got 2 from the above even though we have v.X += 1? This is because Vertex.GetX() has a receiver, not a pointer receiver. Everything after this point will be pointer receivers.

// Note the *Vertex vs Vertex before
func (v *Vertex) GetX() int {
    
    // Note this
    v.X += 1

    return v.X
}

v := Vertex{1, 0}

v.GetX()
v.GetX()
v.GetX()
4

There we got 4, because Vertex.X itself was changed.

Interfaces

In the last section we created a method of Vertex named GetX. In this section we’ll be creating an interface which implements GetX. After that we’ll be type casting the interface back to Vertex so that we can extract X and Y.

type getter interface{
    GetX() int
}

/*  variable     type      Vertex */
var vertexGetter getter = &Vertex{1, 1}

vertexGetter.GetX()
vertexGetter.GetX()
vertexGetter.GetX()
4

Now vertexGetter only has a method GetX. We can no longer access X and Y directly.

vertexGetter.X
repl.go:1:1: type main.getter has no field or method "X": vertexGetter.X
vertex, ok := vertexGetter.(*Vertex)
if ok == false {
    fmt.Println("Type cast failed, got type(%T) expected type(%T)", vertexGetter, new(Vertex))
} else{
    fmt.Printf("Vertex X: %d, Y: %d \n", vertex.X, vertex.Y)
}
Vertex X: 4, Y: 1 

That’s it, we got our original type out from the interface! We also checked ok as a best practice as without it the conversion would panic on failure.

OOP

Some would say Golang is not an Object Oriented Programming language. We’re going to walk through inheritance and method overriding. We’re going to use the classic OOP animal example.

// First we'll define our Animal Object
type Animal struct {
	name string
    sound string
}

// Then we'll defined a method `Sound` which will output the sound the animal makes
func (a *Animal) Sound() {
    fmt.Printf("%s says %s\n", a.name, a.sound)
}

We could simply create an animal which barks and has a name

dog := &Animal{"bob", "bark"}

dog.Sound()
bob says bark

Or we could create a dog struct which inherits from animal, and overrides the sound method.

Inheritance

Create struct Dog which inherits from Animal

type Dog struct {
    *Animal
}
// Now we can call dog.Sound

dog := &Dog{&Animal{"joey", "bark"}}

dog.Sound()
joey says bark

Method Overriding

Now if we want to override the sound method to simply say “Bark! Bark!” we can do so

func (d *Dog) Sound() {
    fmt.Println("Bark! Bark!")
}

dog.Sound()
Bark! Bark!

We can also call the parent method within the overriden method

func (d *Dog) Sound() {
    // Call the parent method
    d.Animal.Sound()

    fmt.Println("Bark! Bark!")
}

dog.Sound()
joey says bark
Bark! Bark!