Object orientation in Go – the struct

In Go, instead of Java and C++ classes, the equivalent container for encapsulation is called a struct. It describes the attributes of the objects for this class. A struct looks like this:

type Animal struct {
    Name string
    canFly bool
  }

This defines a new type with the collection of fields mentioned.

Once you have defined struct, you can instantiate it as follows:

anAnimal := Animal{Name: "Lion", canFly: false}

This creates a new object, anAnimal of type Animal. Once you have an object such as anAnimal, we can use it to access fields using the dot notation as shown here:

   fmt.Println(anAnimal.Name)

You can also use dots with pointers to objects (rather than the actual object). The pointers are automatically dereferenced. So, in the next example, aLionPtr.age works in both cases: aLionPtr being a pointer to an object, as well as being a reference to the object itself:

aLionPtr := &anAnimal
fmt.Println(aLionPtr.age)

Methods are functions that operate on particular struct. They have a receiver clause that mandates what type they operate on. For example, consider the following struct and method:

// This `Person` struct again
type Person struct {
    name string
    age int
 }
func (p Person) canVote() bool {
    return p.Age > 18
 }

In the preceding example, the language construct between the func keyword and the method name is the receiver:

 ( p Person )

This is analogous to the self or this construct of other object-oriented languages. You can view receiver parameters analogous to this or self-identifiers in other languages. There can be only one receiver, and you can define methods using pointer receivers:

 func (t * type) doSomething(param1 int)

And you can use non-pointer method receivers:

 func (t type) doSomething(param1 int)

A pointer receiver method makes for Pass-By-Reference semantics, while a non-pointer one is a Pass-By-Value. Generally, pointer receiver methods are used if either of the following apply:

  • You want to actually modify the receiver (read/write, as opposed to just read).
  • The struct is very large and a deep copy is expensive.

Slices and maps act as references, so even passing them as value will allow mutation of the objects. It should be noted that a pointer receiver method can work with a non-pointer type and vice-versa. For example, the following code will print "11 11", as the DoesNotGrow() is working on a non-pointer receiver, and thus the increment there won't affect the actual value in struct:

package main
import ( "fmt" )
type Person struct { Name string Age int }
func (p *Person) Grow() { p.Age++ }
func (p Person) DoesNotGrow() { p.Age++ }
func main() { p := Person{"JY", 10} p.Grow() fmt.Println(p.Age) ptr := &p ptr.DoesNotGrow() fmt.Println(p.Age) }

This can be confusing for people, but it is clarified in the Go Spec (reference: https://golang.org/ref/spec#Method_sets).

"A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &x value's method set contains m, x.m() is shorthand for (&x).m():."

And if you are wondering what a method set is, the spec defines this as follows:

"The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods with receiver *T or T (that is, it also contains the method set of T)."
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset