LanguagesMethods in Go Explained

Methods in Go Explained

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

In the Go programming language, a method is a function associated with a particular type. This connotation is associated with object-oriented programming (OOP). This article will explain, with examples, where a method is applied in connection with object-oriented features in Go programming.

The term method has been widely used in association with object-oriented programming or OOP. According to object-oriented terminology, an object consists of properties and functions. These functions are also called methods. It is through the method an object exposes its properties. A method typically operates on the data structure and helps in hiding the properties of the object. The availability of the properties of an object through its method is an important aspect of object-oriented programming known as encapsulation.

In Go, we use several methods from the standard library such as the Seconds, Minutes, Round, and so forth of type time.Duration.

Declaring Methods in Go

Unlike normal function declarations, a method declaration is slightly different syntactically. A method name is always prefixed by its variant name. In the case of a normal function, what we do is write the func keyword, then follow the function name, then its parameters and return types, and so on. In the case of a method, since it is always associated with an object, we must provide its variant name immediately after the func keyword declaration. Other aspects are the same as traditional functions. Here is an example:


package main

import (
	"fmt"
	"time"
)

type Employee struct {
	id     int
	name   string
	dob    time.Time
	salary float64
}

//this is normal function
func AddCommission(emp Employee, amount float64) Employee {
	emp.salary += amount
	return emp
}

//this is a method
func (e Employee) AddCommission(amount float64) Employee {
	e.salary = e.salary + amount
	return e
}

func (e Employee) PrintEmployee() {
	fmt.Println("ID: ", e.id)
	fmt.Println("Name: ", e.name)
	fmt.Println("Date of Birth: ", e.dob.Day(), 
        e.dob.Month(), ",", e.dob.Year())
	fmt.Println("Salary: ", e.salary)
}

func main() {

	emp := Employee{101, "Donald Duck", time.Date(1990, 
        12, 20, 0, 0, 0, 0, time.UTC), 7000.00}
	emp.PrintEmployee()
	emp = emp.AddCommission(1200.76)
	emp.PrintEmployee()

	var emp2 Employee
	emp2 = AddCommission(emp, 23000)
	emp2.PrintEmployee()

}

Note that the extra parameter before the method name is called the receiver of the method. You may recall that in C++ or Java, the receiver of a method has a special name such as this. In Go, the receiver is given a name by the programmer, and it’s a convention to keep the name as short as possible due to its frequent use.

Also, note that there is no ambiguity during the function call. The call to AddCommission(…) is a function call while when the call is made as emp.AddCommission(…) it refers to the method associated with the struct type Employee. This is called the selector of the method, which appropriately selects the method for the receiver emp of type Employee.

At this point, a question may arise – can we write methods or functions with the same name but with different parameters? This is a common idea known as function or method overloading. Go does not support function or method overloading in any form, but the idea can be implemented in other ways. For example, multiple versions of fmt.Println methods. We’ll cover that in later writeups.

Go: Using Pointer Receiver

An interesting aspect of the code example above is that the parameters we send to the method are always passed by value; otherwise, why do we need to receive a returned value with the statement?


emp = emp.AddCommission(1200.76)

Understand that a method or a function always makes a copy of each parameter sent to it. As a result, any update made within the scope of the method is absolutely local to it and will not reflect any changes on the fields of the object. Now, suppose we wish to avoid making copies of arguments sent or make changes on the original value of the fields of the invoking object, then we must pass the address of the variable using pointer. We may rewrite the AddCommission(…) method as follows:


func (e *Employee) AddCommission(amount float64) {
	e.salary = e.salary + amount
}

Observe the changes we have made. The receiver now is a pointer and refers to the selector. The signature of the method now does not require to return anything. Any update made on the receiver fields in the method is reflected outside the method scope as well.

Method Accessibility in Composition

Composition is an object-oriented feature where a larger object is created by embedding one or more smaller objects. Each part of the object may have its own set of methods. This is kind of like inheritance, but the relationship between the objects are not parent/child type. The idea is to create a larger object comprising many smaller objects. Since methods are associated with structs, accessibility of methods, while simple, are an important aspect to understand in Go. Let us implement the concept with a simple example.


package main

import "fmt"

type Publisher struct {
	pubName    string
	pubAddress string
}

func (p Publisher) getPublisher() string {
	return p.pubName + ", " + p.pubAddress
}

type Author struct {
	fName string
	mName string
	lName string
}

func (a Author) getAuthor() string {
	return a.fName + " " + a.mName + " " + a.lName
}

type Category struct {
	genre string
	desc  string
}

func (c Category) getCategory() string {
	return c.genre + ", " + c.desc
}

type Title struct {
	title    string
	subtitle string
}

func (t Title) getTitle() string {
	return t.title + ", " + t.subtitle
}

type Book struct {
	title     Title
	author    Author
	publisher Publisher
	category  Category
}

func (b Book) print() {
	fmt.Println(b.title.getTitle())
	fmt.Println(b.author.getAuthor())
	fmt.Println(b.publisher.getPublisher())
	fmt.Println(b.category.getCategory())
}

func main() {
	book := Book{Title{"ABC", "subtitle of ABC"},
		Author{"abc_name", "abc_mnane", "abc_lastname"},
		Publisher{"PUB1", "Somewhere"},
		Category{"Fiction", "Good book"}}
	book.print()
}

This is a composition example. Note how we have created a single large object Book which comprises other smaller objects such as Author, Publisher, Category, and Title. Accessing the methods declared in embedded objects is pretty straightforward using the dot (.) operator. Here we have created a single method called print() which accesses the methods of other composite objects and displays them onto the screen.

Encapsulation in Go

Encapsulation is an important principle of object-oriented programming where the fields inside an object remain inaccessible or hidden to the clients of the object. This is also called information hiding. Encapsulation primarily serves two purposes: the client cannot directly modify an object’s fields, and it hides the implementation details of the methods. The fields of the object can only be accessed through member functions or methods to which they are associated. Popular object-oriented programming languages like C++ or Java have extensive language support. Go, on the other hand, does not have the idea of classes and objects; therefore, the accessibility constraints are achieved through the use of packages.

Go provides two types of identifiers: exported and unexported. Exported identifiers such as variables, structures, functions, methods, and fields are used for implementing encapsulation. The package provides the limit of accessibility and binds the visibility of elements. This means that elements are visible within the package they are defined in.

Identifier names that we want to be exported outside the package must be declared in capital letters. Lower case names are only visible within the package. Here is an example:


package model

type MyStruct struct {
	int rate
}

func NewStruct() *MyStruct {
	return &MyStruct{rate: 0}
}

func (m *MyStruct) getRate() int {
	return m.rate
}

Note that the rate field declared in MyStruct struct is directly accessible by other structs in the same package.


package model

import "fmt"

func MyFunc() {
	m := NewStruct()
	m.rate = 10
	fmt.Println(m.getRate())
}

Now, if we simply change the package name as follows it is no longer accessible.


package othermodel

import "fmt"

func MyFunc() {
	m := NewStruct()
	m.rate = 10
	fmt.Println(m.getRate())
}

Unlike other object-oriented languages, which restrict the scope of names on class level, Go does it through package level. Other object-oriented features like inheritance, association, and so forth can also be implemented in Go using struct and interfaces that we shall see in a later article. Here we have just provided a quick glimpse of the principle.

Final Thoughts

The language Go, interestingly, is a blend of many old and new ideas. It is not an object-oriented language per se, but many features of OOP can be implemented with absolute ease. The difference between method and function is actually minimal. The concept is that methods are associated with objects and defines their behavior, whereas functions are not. Go has put some syntactical flavor in defining a method that makes it look a bit different from function. But the idea at the core of elaborating our logic is pretty much the same.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories