Object-oriented Programming in Go

 • 

Hi, mate! Today we are going to learn about object-oriented programming in Go. These are two interesting aspects, considering Go has been so favoured at the moment and object-oriented is one of the most prominent paradigm in the programming world. Before we start to discuss about this topic, let’s have a look at how Google define the object-oriented in Go.

Source: Frequently Asked Questions (FAQ) - The Go Programming Language.

So does Go support object-oriented? Well, personally I would not say this is an object-oriented language–since the term of object is not available in Go, it is classified better as procedural–however Go has something called interface that allows us to do object-oriented approaches in a simpler manner.

As we have already known, Google really preserve the simplicity of this language. Go has no classes, no objects, no exceptions, and no templates. It also provides a built-in concurrency, which is one of the Go’s strongest features. As a substitute for class and object in other languages such as C++ or Java, Go has struct, which is used generally to define data type.

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

// Greet prints an output with Person as the receiver
func (p *Person) Greet() {
	fmt.Printf("Hi I'm %s, %d years old\n", p.Name, p.Age)
}

// Work is an abstract method and implements no action
func (p *Person) Work() {
	// do nothing
}

func main() {
	p := Person{Name: "Frank", Age: 38}
	p.Greet()
	p.Work()
}
Hi I'm Frank, 38 years old

In this example, we have a struct of Person which contains Name and Age. We can create some methods using Person as the receiver. Notice that we only add an action in Greet, so even though Work is also called, nothing is printed in the result.

Now we want to create a new struct named Man that inherits Person.

package main

import "fmt"

// Man is a child struct of Person
type Man struct {
	Person
	Salary float64
}

// Work is overridden to be used by Man
func (m *Man) Work() {
	fmt.Printf("%s works for ‎£%.f\n", m.Name, m.Salary)
}

func main() {
	m := Man{}
	m.Name = "John"
	m.Age = 36
	m.Salary = 150000.0

	m.Greet()
	m.Work()
}
Hi I'm John, 36 years old
John works for ‎£150000

By compositing Person into this new struct, automatically Man will be granted the accessibility to all fields and methods of Person, or as we like to call as a parent. Besides the fields that have been existed in Person, we can also add a new one which is Salary in this case. Man shares the same Greet method but as a substitute for Work that still does no action in Person, we override it. Now that we have overridden Work, we can see it has some action when we call it.

Next we can create another struct of Woman that has completely different behaviours from Man.

package main

import "fmt"

// Woman is a child struct of Person
type Woman struct {
	Person
	IsWorking bool
}

// Greet is overridden to be used by Woman
func (w *Woman) Greet() {
	fmt.Printf("Hello my name is %s, nice to meet you\n", w.Name)
}

// Work is overridden to be used by Woman
func (w *Woman) Work() {
	if w.IsWorking {
		fmt.Println(w.Name, "is working")
	} else {
		fmt.Println(w.Name, "is unemployed")
	}
}

func main() {
	w := Woman{}
	w.Name = "Eva"
	w.Age = 43
	w.IsWorking = false

	w.Greet()
	w.Work()
}
Hello my name is Eva, nice to meet you
Eva is unemployed

Instead of Salary that may not be an important aspect to Woman, we define a new field called IsWorking and it will be used in the overridden method Work. For example we have a condition where Woman does not want to disclose Age in Greet, therefore we also need to override it to accommodate the new action.

Then what if we do not want to use struct as the receiver but we want to call it in the argument? Let’s say we want to create a new function named Introduce that can be accessible by Person and all its children. This function will execute all methods that we have been discussing previously with the addition of a new method Hobby that is only declared in Person.

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

// Greet prints an output with Person as the receiver
func (p *Person) Greet() {
	fmt.Printf("Hi I'm %s, %d years old\n", p.Name, p.Age)
}

// Work is an abstract method and implements no action
func (p *Person) Work() {
	// do nothing
}

// Hobby prints an output with h as the argument
func (p *Person) Hobby(h string) {
	fmt.Println("I like", h)
}

// Man is a child struct of Person
type Man struct {
	Person
	Salary float64
}

// Work is overridden to be used by Man
func (m *Man) Work() {
	fmt.Printf("%s works for ‎£%.f\n", m.Name, m.Salary)
}

// Woman is a child struct of Person
type Woman struct {
	Person
	IsWorking bool
}

// Greet is overridden to be used by Woman
func (w *Woman) Greet() {
	fmt.Printf("Hello my name is %s, nice to meet you\n", w.Name)
}

// Work is overridden to be used by Woman
func (w *Woman) Work() {
	if w.IsWorking {
		fmt.Println(w.Name, "is working")
	} else {
		fmt.Println(w.Name, "is unemployed")
	}
}

// Introduce executes Greet, Work and Hobby from Person
func Introduce(p Person, h string) {
	p.Greet()
	p.Work()
	p.Hobby(h)
}

func main() {
	m := Man{}
	m.Name = "John"
	m.Age = 36
	m.Salary = 150000.0

	w := Woman{}
	w.Name = "Eva"
	w.Age = 43
	w.IsWorking = false

	Introduce(&m, "to play golf")
	Introduce(&w, "shopping")
}
main.go:72: cannot use &m (type *Man) as type Person in argument to Introduce
main.go:73: cannot use &w (type *Woman) as type Person in argument to Introduce

So what happens when we call Introduce using structs other than Person? Go does not accept types of Man and Woman, even though both are the inheritances of Person. To be able to do polymorphism in this situation, we need to implement an interface.

type PersonDriver interface {
	Greet()
	Work()
	Hobby(string)
}

// Introduce executes Greet, Work and Hobby from PersonDriver
func Introduce(p PersonDriver, h string) {
	p.Greet()
	p.Work()
	p.Hobby(h)
}

Now as a replacement for Person in the argument to Introduce, we use the PersonDriver interface. Interface is used to declare a set of methods that can be applied. If we do not include some methods in the PersonDriver–although those methods are available in Person–we cannot call them from PersonDriver. By using an interface, we can call a function using any types in the argument as long as the structs have the same methods used in the function.

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

// Greet prints an output with Person as the receiver
func (p *Person) Greet() {
	fmt.Printf("Hi I'm %s, %d years old\n", p.Name, p.Age)
}

// Work is an abstract method and implements no action
func (p *Person) Work() {
	// do nothing
}

// Hobby prints an output with h as the argument
func (p *Person) Hobby(h string) {
	fmt.Println("I like", h)
}

// Man is a child struct of Person
type Man struct {
	Person
	Salary float64
}

// Work is overridden to be used by Man
func (m *Man) Work() {
	fmt.Printf("%s works for ‎£%.f\n", m.Name, m.Salary)
}

// Woman is a child struct of Person
type Woman struct {
	Person
	IsWorking bool
}

// Greet is overridden to be used by Woman
func (w *Woman) Greet() {
	fmt.Printf("Hello my name is %s, nice to meet you\n", w.Name)
}

// Work is overridden to be used by Woman
func (w *Woman) Work() {
	if w.IsWorking {
		fmt.Println(w.Name, "is working")
	} else {
		fmt.Println(w.Name, "is unemployed")
	}
}

type PersonDriver interface {
	Greet()
	Work()
	Hobby(string)
}

// Introduce executes Greet, Work and Hobby from PersonDriver
func Introduce(p PersonDriver, h string) {
	p.Greet()
	p.Work()
	p.Hobby(h)
}

func main() {
	m := Man{}
	m.Name = "John"
	m.Age = 36
	m.Salary = 150000.0

	w := Woman{}
	w.Name = "Eva"
	w.Age = 43
	w.IsWorking = false

	Introduce(&m, "to play golf")
	Introduce(&w, "shopping")
}
Hi I'm John, 36 years old
John works for ‎£150000
I like to play golf
Hello my name is Eva, nice to meet you
Eva is unemployed
I like shopping

As a conclusion, despite Go is not an object-oriented language, we can still apply the similar approaches using struct and interface. By these brief explanations, I hope you can get a clear view and bring out more usability of the features in Go. In the upcoming post you are going to learn about unit testing in Go. Cheers!