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).
And if you are wondering what a method set is, the spec defines this as follows: