Methods and Objects


Go methods

A method is defined just like any other Go function. When a Go function is defined with a limited scope or attached with a specific type it is known as a method. Methods provide a way to add behavior to user-defined types. Methods are really functions that contain an extra parameter that's declared between the keyword func and the function name.

In Go a method is a special kind of function that acts on variable of a certain type, called the receiver, which is an extra parameter placed before the method's name, used to specify the moderator type to which the method is attached. The receiver type can be anything, not only a struct type: any type can have methods, even a function type or alias types for int, bool, string or array.

The general format of a method is:
func (recv receiver_type) methodName(parameter_list) (return_value_list) { … }
The receiver is specified in ( ) before the method name after the func keyword.

Here is an example of methods on a non struct type:

Example

package main
import "fmt"

type multiply int	

func (m multiply) tentimes() int {
	return int(m * 10)
}

func main() {
	var num int
	fmt.Print("Enter any positive integer: ")
    fmt.Scanln(&num)
	mul:= multiply(num)
	fmt.Println("Ten times of a given number is: ",mul.tentimes())
}

Output

Enter any positive integer: 5
Ten times of a given number is:  50

The parameter between the keyword func and the function name is called a receiver and binds the function to the specified type. When a function has a receiver, that function is called a method.

There are two types of receivers in Go: value receivers and pointer receivers. In above program the tentimes method is declared with a value receiver. The receiver for tentimes is declared as a value of type int. When you declare a method using a value receiver, the method will always be operating against a copy of the value used to make the method call.

The mul variable is initialized as the multiply type. Therefore, the tentimes method can be accessed using mul.tentimes() when we call the tentimes method, the value of mul is the receiver value for the call and the tentimes method is operating on a copy of this value.

Here's a program that shows implementation of number of methods attached to a type, via the receiver parameter, which is known as the type's method set.

Example

package main 
import "fmt" 
 
type salary float64 
func (s salary) total() total {    
   return total(s) 
} 
 
type total float64 
func (t total) hra() hra {
   t += t * 0.3   // 30% HRA Addition
   return hra(t)
} 
func (t total) salary() salary {   
   t -=t * 0.10    // 10% Tax Deduction  
   return salary(t) 
} 
 
type hra float64
func (h hra) basic() basic {
   h += h * 0.3   // 30% HRA Addition
  return basic(h)
}
func (h hra) total() total {   
  return total(h)
}

type basic float64 
func (b basic) total() total { 
   return total(b) 
} 
 
func main() { 
    fmt.Println("Salary calculation for First Employee:")
    sal1 := basic(9000.00)
    fmt.Println(sal1.total())
    fmt.Println(sal1.total().hra().total())
    fmt.Println(sal1.total().hra().total().salary())

    fmt.Println("\nSalary calculation for Second Employee:")
    sal2 := basic(5000.00)
    fmt.Println(sal2.total())
    fmt.Println(sal2.total().salary())
}

Output

Salary calculation for First Employee:
9000
11700
10530

Salary calculation for Second Employee:
5000
4500

Method Overloading

Method Overloading is possible based on the receiver type, a method with the same name can exist on 2 of more different receiver types,e.g. this is allowed in the same package:
func (s *inclTax) Salary(e Employee) Employee
func (s *exclTax) Salary(e Employee) Employee

Receiver parameters can be passed as either values of or pointers of the base type. Pointer receiver parameters are widely used in Go.

You can also declare methods with pointer receivers.

Example

package main
import "fmt" 

type multiply int 
type addition int 

func (m *multiply) twice() { 
  *m = multiply(*m * 2) 
}

func (a *addition) twice() { 
  *a = addition(*a + *a)
} 

func main() { 
  var mul multiply = 15 
  mul.twice()
  fmt.Println(mul)

	
  var add addition = 15 
  add.twice()
  fmt.Println(add)
}

Output

30
30

Let's take another example in which we call to a method which receives a pointer to Struct. We call to that method by value and by pointer and we got the same result.

Example to declare methods with pointer receivers.

Example

package main
import "fmt" 

type multiply struct {
	num int 
}

func (m *multiply) twice(n int) { 
  	m.num = n*2
}

func (m multiply) display() int{ 
  	return m.num
} 

func main() { 
  fmt.Println("Call by value")	
  var mul1 multiply	// mul1 is a value
  mul1.twice(10)
  fmt.Println(mul1.display())

  fmt.Println("Call by pointer")	
  mul2 := new(multiply) // mul2 is a pointer
  mul2.twice(10)
  fmt.Println(mul2.display())
}

Output

Call by value
20
Call by pointer
20

In the main() we ourselves do not have to figure out whether to call the methods on a pointer or not, Go does that for us. mul1 is a value and mul2 is a pointer, but the methods calls work just fine.


Objects in Go

There is no concept of a class type that serves as the basis for objects in Go. Any data type in Go can be used as an object.struct type in Go can receive a set of method to define its behavior. There is no special type called a class or object exist in GO. strct type in Go comes the closet to what is commonly refer as object in other programming language. Go supports the majority of concepts that are usually attributed to object-oriented programming.

With the help of concepts like packages and an extensible type system Go supports physical and logical modularity at its core; hence we able to achieve Modularity and encapsulation in Go.

A newly declared name type does not inherit all attributes of its underlying type and are treated variously by the type system. Hence GO doesn't support polymorphism through inheritance.But it is possible to create objects and express their polymorphic relationships through composition using a type such as a struct or an interface.

Let us start with the following simple example to demonstrate how the struct type may be used as an object that can achieve polymorphic composition.

Example

package main
import "fmt"

type gadgets uint8 
const (    
    Camera gadgets = iota
    Bluetooth
    Media
    Storage
    VideoCalling
    Multitasking
    Messaging
) 
type mobile struct { 
    make string 
    model string 
} 
 
type smartphone struct { 
   gadgets gadgets    
}

func (s *smartphone) launch() {
   fmt.Println ("New Smartphone Launched:") 
} 
 
type android struct { 
   mobile 
   smartphone
   waterproof string
} 
func (a *android) samsung() { 
   fmt.Printf("%s %s\n", 
          a.make, a.model)           
} 
 
type iphone struct { 
   mobile 
   smartphone 
   sensor int
} 
func (i *iphone) apple() { 
   fmt.Printf("%s %s\n",
          i.make, i.model) 
} 


func main() { 
   t := &android {}
   t.make ="Samsung"
   t.model ="Galaxy J7 Prime"
   t.gadgets = Camera+Bluetooth+Media+Storage+VideoCalling+Multitasking+Messaging
   t.launch()
   t.samsung() 
}

Output

New Smartphone Launched:
Samsung Galaxy J7 Prime

In above program the composition over inheritance principle is used to achieve polymorphism using the type embedding mechanism supported by the struct type. Here each type is independent and is considered to be different from all others. The above program show that the types iphone and android is a mobile via a subtype relationship.

The methods t.launch() being invoked however, neither type, iphone nor android, are receivers of a method named launch(). The launch()method is defined for the smartphone type. Since the smartphone type is embedded in the types iphone nor android, the launch() method is promoted in scope to these enclosing types and is therefore accessible.