We learned how to write methods in Go, that they can be seen as functions with an implicit first parameter that is its receiver, and we saw some facilities that Go offers when working with pointers receivers.
In this chapter we will see how to implement some OOP concepts with methods. It will be fun and relatively easy.
I didn't tell you about this when we studied struct
s, but we actually can
declare fields without specifying names for them (just the type) We call them
anonymous fields for this reason (lack of name).
When this anonymous field is a struct
its fields are inserted or embedded
into the struct containing it.
Let's see an example to clarify this:
package main
import "fmt"
type Human struct{
name string
age int
weight int //in lb, mind you
}
type Student struct{
Human //an anonymous field of type Human
speciality string
}
func main(){
//Mark is a Student, look how we declare a literal of this type
mark := Student{Human{"Mark", 25, 120}, "Computer Science"}
//Now let's access the fields:
fmt.Println("His name is", mark.name)
fmt.Println("His age is", mark.age)
fmt.Println("His weight is", mark.weight)
fmt.Println("His speciality is", mark.speciality)
//Change his speciality
mark.speciality = "AI"
fmt.Println("Mark changed his speciality")
fmt.Println("His speciality is", mark.speciality)
//change his age. He got older
fmt.Println("Mark become old")
mark.age = 46
fmt.Println("His age is", mark.age)
//Mark gained some weight as time goes by
fmt.Println("Mark is not an athlet anymore")
mark.weight += 60
fmt.Println("His weight is", mark.weight)
}
Output:
On line 29 and 33 we were able to access and change the fields age
and
weight
like if they were declared fields of Student
. This is because
fields of Human
are inserted in the struct Student
.
Cool, isn't it? But there's more than this interpolation of fields, an anonymous field can be used with its type as its name too!
This means that Student
has a field named Human
that can be used as
regular field with this name.
//you can access and modify the Human field by this name
mark.Human = Human{"Marcus", 55, 220} //he changed his personality
mark.Human.age -= 1 //goes back in time
Accessing, and changing an anonymous field with is name is quite useful for
anonymous fields that are not struct
s. ---Who told you that anonymous
fields must be struct
s? --I didn't!
In fact, any named type and pointers to named ones are welcome too.
Example:
package main
import "fmt"
type Skills []string
type Human struct{
name string
age int
weight int //in lb, mind you
}
type Student struct{
Human //an anonymous field of type Human
Skills //anonymous field for his skills
int //we will use this int as an anonymous field for his preferred number
speciality string
}
func main(){
//Jane is a Student, look how we declare a literal of this type
//by specifying only some of its fields. We saw this before
jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"}
//Now let's access the fields:
fmt.Println("Her name is", jane.name)
fmt.Println("Her age is", jane.age)
fmt.Println("Her weight is", jane.weight)
fmt.Println("Her speciality is", jane.speciality)
//Let's change some anonymous fields
jane.Skills = []string{"anatomy"}
fmt.Println("Her skills are", jane.Skills)
fmt.Println("She acquired two new ones")
jane.Skills = append(jane.Skills, "physics", "golang")
fmt.Println("Her skills now are", jane.Skills)
//her preferred number, which is an int anonymous field
jane.int = 3
fmt.Println("Her preferred number is", jane.int)
}
Output:
This anonymous field mechanism lets us inherit some (or even all) implementation of a given type from another type or types.
What happens if Human
has a field phone
and Student
has also a field
with the same name?
This kind of conflicts are solved in Go simply by saying that the outer name
hides the inner one. i.e. when accessing phone
you are working with
Student
's one.
This provides a way to override a field that is present in the "inherited" anonymous field by the one that we explicitely specify in our type.
If you still need to access the anonymous one, you'll have to use the type's name syntax.
Example:
package main
import "fmt"
type Human struct{
name string
age int
phone string //his own mobile number
}
type Employee struct{
Human //an anonymous field of type Human
speciality string
phone string //work's phone number
}
func main(){
Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"}
fmt.Println("Bob's work phone is: " Bob.phone)
//Now we need to specify Human to access Human's phone
fmt.Println("Bob's personal phone is: " Bob.Human.phone)
}
Output:
Great! Now, what does all this have to do with methods? Attaboy, you didn't forget the main subject of the chapter.
The good surprise is that methods behave exactly like anonymous fields. If an anonymous field implements a given method, this method will be available for the type that is using this anonymous field.
If the Human
type implements a method SayHi()
that prints a greeting,
this same method is available for both Student
and Employee
! You won't
need to write it twice for them.
package main
import "fmt"
type Human struct{
name string
age int
phone string //his own mobile number
}
type Student struct{
Human //an anonymous field of type Human
school string
}
type Employee struct{
Human //an anonymous field of type Human
company string
}
//A human method to say hi
func (h *Human) SayHi(){
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
func main(){
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
mark.SayHi()
sam.SayHi()
}
Output:
Philosophy
Think about it: you often need to represent things that share some attributes or characteristics, this is solved with the anonymous field mechanism. And when you need to represent a shared behavior or functionality, you can use a method on an anonymous field.
What if you want the Employee
to tell you where he works too? Easy, the same
rule for overriding fields on conflict applies for methods, and we happily
exploit it.
package main
import "fmt"
type Human struct{
name string
age int
phone string
}
type Student struct{
Human //an anonymous field of type Human
school string
}
type Employee struct{
Human //an anonymous field of type Human
company string
}
//A human method to say hi
func (h *Human) SayHi(){
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
//Employee's method overrides Human's one
func (e *Employee) SayHi(){
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //Yes you can split into 2 lines here.
}
func main(){
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
mark.SayHi()
sam.SayHi()
}
Output:
And... It worked like a charm!
With these simple concepts, you can now design shorter, nicer and expressive programs. In the next chapter we will learn how to enhance this experience even more with a new notion: Interfaces.