Friday, June 24, 2022
HomeSoftware DevelopmentIntroduction to Object-oriented Programming in Go

Introduction to Object-oriented Programming in Go


Unlike many popular languages like Java and C++, Go is not an object oriented language per se. In the Go programming language, developers need to force create object-oriented style with the minimum language support that it provides. Understand that the paradigm of object-oriented technology is more conceptually imbibed within the build of the language that supports it. That being said, most general-purpose languages that may not be object-oriented can mimic object-oriented principles. A programming language may inherently have very little or limited support in implementing the principles of object-oriented programming. However, a pure object-oriented language is more friendly to object-oriented principles and has an explicit support of its features, such as inheritance, polymorphism, encapsulation, and so forth. Here, in this programming tutorial, we will introduce some of the basic OOP features of Go and the Golang programming language.

Read: Understanding Garbage Collection in Go

Is Go an OOP Language?

Go is not an OOP language – that much is for sure. But, can we use go language features to meet the principles of the object-oriented methodologies? Yes, developers can, to some extent. For example, Go has no support of inheritance, but the idea can be compensated for with its support of composition. Similarly, a kind of polymorphism can be created using interfaces. Understand that, if one truly wants to develop an application according to the object-oriented methodologies, Go is definitely not a good choice. Rather, one should look for languages like Java, C++, Python or any other high functioning language that supports object-oriented features.

If you are interested in using Interfaces in Go to mimic polymorphism and OOP concepts, you can check out our tutorial: Introduction to Interfaces in Go.

How to Create Objects in Go and Golang

The key idea behind object-orientation is to separate the code into several manageable parts or objects. Each object has its own identity that is determined by the data (attributes) and the methods (behavior) that operate on the data. The attributes are typically hidden and can only be accessed through the methods defined within the object. This is called encapsulation. Unlike Java or C++, both of which use classes to create the container or abstract data type, Go can encapsulate its attributes using struct.

Object-oriented programming in Golang

In the above image, the object description may look something like this Go example code:

package payroll

type Employee struct {
	Id    int
	name  string
	phone string
	email string
}

The above Golang code defines the abstract data type with attributes. Once it has been created we can instantiate it as follows:

e1 := payroll.Employee{Id: 101}

or:

e1 := payroll.Employee{}

or:

var ep1 *payroll.Employee
ep1 = &payroll.Employee{Id: 102}
ep1.SetEmployee("Anita", "4787753", "[email protected]")

Data Hiding in Go

But, what if we write the code as follows:

e1 := payroll.Employee{Id: 101, name:"Anita",phone:"2783648",email:"[email protected]" } // Error!

This is not going to work. The reason is accessibility. Unlike Java or C++, where we have private or public to restrict the accessibility of attributes, Go has no such features. Instead, what it provides is a means for package-level accessibility. Note that we have written Id in the Employee structure, with the first letter in uppercase. This means that the attribute Id can be accessed directly outside the package when the object is created, while the attributes such as name, phone, and email are written in lowercase and can only be accessed through member methods, which, in Go, are defined as follows:

func (e *Employee) GetName() string {
	return e.name
}
func (e *Employee) SetName(name string) {
	e.name = name
}

The above code provides a means to implement a sort of encapsulation in Go. To quickly test the idea, here is the complete code:

package payroll

type Employee struct {
	Id    int
	name  string
	phone string
	email string
}

func (e *Employee) SetEmployee(name, phone, email string) {
	e.SetName(name)
	e.SetPhone(phone)
	e.SetEmail(email)
}

func (e *Employee) GetId() int {
	return e.Id
}
func (e *Employee) GetName() string {
	return e.name
}
func (e *Employee) GetPhone() string {
	return e.phone
}
func (e *Employee) GetEmail() string {
	return e.email
}
func (e *Employee) SetId(id int) {
	e.Id = id
}
func (e *Employee) SetName(name string) {
	e.name = name
}
func (e *Employee) SetPhone(phone string) {
	e.phone = phone
}
func (e *Employee) SetEmail(email string) {
	e.email = email
}

//-------------------------------------------------------------
package main

import (
	"fmt"
	"oop_prog/payroll"
)

func main() {
	e1 := payroll.Employee{Id: 101}
	e1.SetEmployee("Bob", "82347783", "[email protected]")
	fmt.Println(e1.GetId(), ":", e1.GetName(), ":", e1.GetPhone(), ":", e1.GetEmail())
}

Want to learn more about struct? We have a programming tutorial that will teach you more: Understanding Structs in Go.

Inheritance by Composition in Go

As inheritance is not supported directly in Go, what programmers can do is use composition to get some of the effect of it. Composition basically means putting one or more objects into another object like an attribute. Therefore, we can create a struct type that embeds other struct types. For example, a computer is a composition of components like CPU, RAM, HDD, a Motherboard and other stuff.

Here is some example code demonstrating inheritance by composition in Go:

package computer

import "fmt"

type CPU struct {
	architecture string
}

func (cpu *CPU) SetArchitecture(arch string) {
	cpu.architecture = arch
}

func (cpu *CPU) GetArchitecture() string {
	return cpu.architecture
}

type RAM struct {
	size int
}

func (ram *RAM) SetSize(size int) {
	ram.size = size
}

func (ram *RAM) GetSize() int {
	return ram.size
}

type Motherboard struct {
	category string
}

func (m *Motherboard) SetCategory(cat string) {
	m.category = cat
}

func (m *Motherboard) GetCategory() string {
	return m.category
}

type Computer struct {
	cpu    CPU
	ram    RAM
	mboard Motherboard
}

func (c *Computer) SetSpecification(cpu CPU, ram RAM, mboard Motherboard) {
	c.cpu.SetArchitecture(cpu.GetArchitecture())
	c.ram.SetSize(ram.GetSize())
	c.mboard.SetCategory(mboard.GetCategory())
}

func (c *Computer) ShowSpecification() {
	fmt.Println("CPU: ", c.cpu.GetArchitecture(), ", RAM: ", c.ram.GetSize(), "GB, Motherboard: ", c.mboard.GetCategory())
}

//-------------------------------------

package main

import (
	"fmt"
	"oop_prog/computer"
)

func main() {
	cpu := computer.CPU{}
	cpu.SetArchitecture("64-bit")

	ram := computer.RAM{}
	ram.SetSize(8)

	mboard := computer.Motherboard{}
	mboard.SetCategory("Micro ATX")

	c1 := computer.Computer{}
	c1.SetSpecification(cpu, ram, mboard)
	c1.ShowSpecification()
}

The problem with normal inheritance is that it leads to a hierarchy of classes. The behavior of the final object is often buried or spread across the hierarchy. Moreover, any change in the superclass, or in any class in the hierarchy, can have a ripple effect. A bug crept into the branches of inheritance is very difficult to debug. The composition used in Go is actually a fine way to delegate behavior of the object. According to object-oriented methodology, composition creates a has-a relationship among objects, whereas in normal inheritance the relationship among the object is called an is-a relationship.

The beauty of composition is that many simple object constructs can be combined to create a more complex object only when needed. It means that developers can lazy-create objects only when needed. This keeps memory allocation to a minimum, leading to a lesser memory footprint of the program.

In our example above, we have created objects like CPU, RAM and Motherboard and defined their attributes and methods. As we create a Computer, we can combine the individual objects into a more complex object.

Read: Understanding Garbage Collection in Go

Polymorphism in Go

Polymorphism is another key feature of object oriented programming. It provides the ability to write code through the implementation of types that can take on different behavior at runtime. In Go, polymorphism is achieved through interfaces and interfaces only. Once an interface implements a type, the functionality defined within it is open to any values of that type. The standard library is replete with this implementation. For example, the io package has an extensive set of interfaces and functions to stream data efficiently in our code. Here is a quick example to illustrate how polymorphic behavior is achieved through interfaces in Go:

package polymorph

import (
	"fmt"
	"math"
)

type Shape interface {
	area()
}

type Rectangle struct {
	X1, Y1, X2, Y2 float64
}

type Circle struct {
	Xc, Yc, Radius float64
}

func (r *Rectangle) area() {
	fmt.Println("Rectangle Area : ", (r.X2-r.X1)*(r.Y2-r.Y1))
}

func (c *Circle) area() {
	fmt.Println("Circle Area: ", math.Pi*math.Pow(c.Radius, 2))
}

func GetArea(s Shape) {
	s.area()
}

//--------------------------------------------------

package main

import "oop_prog/polymorph"
func main() {
	r := polymorph.Rectangle{10, 10, 20, 20}
	polymorph.GetArea(&r)
	c := polymorph.Circle{5, 5, 30}
	polymorph.GetArea(&c)

}

In the code above the two struct types Rectangle and Circle implement the Shape interface. As a result, the struct becomes type Shape and it can be passed to the function GetArea. In this way, we can add as many struct types as we want or need; as long as it implements the Shape interface, the dynamic behavior of the GetArea method will be decided at runtime.

As an aside, you can learn more about the io package in this Golang programming tutorial: How to Perform Basic I/O Operations in Go.

Final Thoughts on OOP and Go

Most of the object-oriented methodologies are actually conceptual. Therefore, if the design is thought out carefully, doing object-oriented programming in any language is actually possible. Pure object-oriented languages just provide extra support. Although implementing every object-oriented feature is not possible in Go, some of the key features can easily be implemented. Forget about object-oriented, these features can be used in any way the programmer likes. That is actually the crux of the matter.

Read more Go and Golang programming tutorials.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments