There’s no class, no inheritance, no polymorphism in Go,
but we can still achieve OOP with struct, method and interface.

The only way to abstract is composition.

Structs

There is NO CLASS in Go, but structs is supported.
They are also useful for grouping data together.

package main
import "fmt"
 
type person struct {
 name string
 age  int
}
// Define a struct `person` with two fields: `name` (string) and `age` (int)
 
func newPerson(name string, age int) *person {
 p := person{age: age}
 p.name = "Mr. " + name
 return &p
}
// Define a constructor.
// To initialize a struct, use `structName{structField: value, ...}`
// Access a field in a struct by `structVariable.structField`
// Return the pointer of struct variable defined and initialized in constructor.
// There's GC in Go, we don't need to worry about memory leaking.
 
func main() {
 fmt.Println(person{"Alice", 30})
 fmt.Println(person{name: "Bob", age: 35})
 fmt.Println(person{name: "Charlie"})        // Omitted fields will be zero-valued
 fmt.Println(&person{name: "Dave", age: 40}) // & prefix yields a pointer to the struct
 fmt.Println(newPerson("Eve", 25))           // Idiomatic way with constructor
 
 s := person{"Frank", 28}
 fmt.Println(s.name) // Access struct fields with dot notation
 
 sp := &s
 fmt.Println(sp.age)
 // Structs are mutable
 
 // Access struct fields through pointer with dot notation,
 // it will be dereferenced automatically.
 sp.age = 29
 fmt.Println(sp.age)
 
 dog := struct {
  name string
  age  int
 }{
  "Buddy",
  3,
 }
 fmt.Println(dog.name) // Anonymous struct
}
/*
{Alice 30}
{Bob 35}
{Charlie 0}
&{Dave 40}
&{Mr. Eve 25}
Frank
28
29
Buddy
*/

Methods

package main
import "fmt"
 
type rect struct {
 height, width int
}
 
func (r *rect) area() int {
 return r.height * r.width
}
// Define a method has a *receiver type* of `*rect`
 
func (r rect) perim() int {
 return 2*r.height + 2*r.width
}
// Method can also be defined with a *receiver type* of `rect` (not a pointer)
 
// If use pointer, we can change the state of the struct,
// decrease the cost of copying the struct when the struct is large.
// If use value, it cannot be changed, and will be copied when the method is called.
 
func main() {
 r1 := rect{10, 5}
 fmt.Println("area: ", r1.area())
 fmt.Println("perim: ", r1.perim())
 
 r2 := &rect{3, 7}
 fmt.Println("area: ", r2.area())
 fmt.Println("perim: ", r2.perim())
 // But whatever which is used, value and pointer can call these methods
 // by automatically dereferencing the pointer or taking the address of the value.
}
/*
area:  50
perim:  30
area:  21
perim:  20
*/

Interface

Go supports duck type through interfaces.

package main
import (
 "fmt"
 "math"
)
 
type geometry interface {
 area() float64
 perim() float64
}
 
type rect struct {
 width, height float64
}
 
type circle struct {
 radius float64
}
 
func (r rect) area() float64 {
 return r.width * r.height
}
 
func (r rect) perim() float64 {
 return 2*r.width + 2*r.height
}
 
// To implement an interface, we just need to implement all the methods in the interface
// without the need of any explicit declaration. No `implement` keyword is needed in Go.
 
func (c circle) area() float64 {
 return math.Pi * c.radius * c.radius
}
 
func (c circle) perim() float64 {
 return 2 * math.Pi * c.radius
}
 
func measure(g geometry) {
 fmt.Println(g)
 fmt.Println("Area: ", g.area())
 fmt.Println("Perimeter: ", g.perim())
}
 
// If a variable has an interface type, then we can call methods that are in the interface.
 
func detectShape(g geometry) {
 switch t := g.(type) {
 case rect:
  fmt.Println("This is a rectangle with width", t.width, "and height", t.height)
 case circle:
  fmt.Println("This is a circle with radius", t.radius)
 }
}
 
// Sometimes, runtime type is important.
// As what we had introduced in the previous section,
// we can use type assertion to get the dynamic type of an interface variable.
 
func main() {
 r := rect{width: 3, height: 4}
 c := circle{radius: 5}
 
 measure(r)
 measure(c)
 
 detectShape(r)
 detectShape(c)
}
/*
{3 4}
Area:  12
Perimeter:  14
{5}
Area:  78.53981633974483
Perimeter:  31.41592653589793
This is a rectangle with width 3 and height 4
This is a circle with radius 5
*/