Although Golang does not support inheritance, we can achieve similar results through the usage of type definitions and type embedding.
Let's start with a simple object: we need to store contacts data:
and birthday will be composed by:
Our struct will be defined as below:
type birthday struct {
day int16
month int16
year int16
}
type contact struct {
name string
surname string
birthday birthday
}
func (c *contact) FullName() string {
return fmt.Sprintf("%s %s", c.name, c.surname)
}
Now we want to define a new struct for European contacts that will inherit from contact
and will add a Birthday
method returning a string in the format 'dd/mm/yyyy'.
We could try the following approach:
package main
import "fmt"
type birthday struct {
day int16
month int16
year int16
}
type contact struct {
name string
surname string
birthday birthday
}
func (c *contact) FullName() string {
return fmt.Sprintf("%s %s", c.name, c.surname)
}
// *** HERE WE DECLARE THE NEW EUContact
type EUContact = contact
func (euc *EUContact) Birthday() string {
return fmt.Sprintf("%d/%d/%d", euc.birthday.day, euc.birthday.month, euc.birthday.year)
}
func main() {
euc := EUContact{
name: "Massimiliano",
surname: "Ziccardi",
birthday: birthday{
day: 14,
month: 6,
year: 1976,
},
}
fmt.Println(euc.FullName())
fmt.Println(euc.Birthday())
}
--- OUTPUT ---
Massimiliano Ziccardi
14/6/1976
Great! It worked. Perfect. Now we need to add a new USContact
type with a Birthday
method returning a string in the format 'mm/dd/yyyy'.
Let's try with the same approach as before:
package main
import "fmt"
type birthday struct {
day int16
month int16
year int16
}
type contact struct {
name string
surname string
birthday birthday
}
func (c *contact) FullName() string {
return fmt.Sprintf("%s %s", c.name, c.surname)
}
type EUContact = contact
func (euc *EUContact) Birthday() string {
return fmt.Sprintf("%d/%d/%d", euc.birthday.day, euc.birthday.month, euc.birthday.year)
}
type USContact = contact
func (usc *USContact) Birthday() string {
return fmt.Sprintf("%d/%d/%d", usc.birthday.month, usc.birthday.day, usc.birthday.year)
}
func main() {
euc := EUContact{
name: "Massimiliano",
surname: "Ziccardi",
birthday: birthday{
day: 14,
month: 6,
year: 1976,
},
}
usc := USContact{
name: "John",
surname: "Doe",
birthday: birthday{
day: 16,
month: 9,
year: 1981,
},
}
fmt.Println(euc.FullName())
fmt.Println(euc.Birthday())
fmt.Println(usc.FullName())
fmt.Println(usc.Birthday())
}
This time something went wrong. The compiler returned an error:
./prog.go:29:23: contact.Birthday redeclared in this block
./prog.go:23:23: other declaration of Birthday
Why? The key is that with
type EUContact = contact
we declared just an alias for contact
, not a new type. So, when we declared USContact
it was just another alias and when we tried to declare another Birthday
method we got the duplicate method error.
How do we solve this issue? The key is the =
sign in the statement: if we remove it, our type will become a new type instead than being just an alias.
Let's remove it and see what happens
package main
import "fmt"
type birthday struct {
day int16
month int16
year int16
}
type contact struct {
name string
surname string
birthday birthday
}
func (c *contact) FullName() string {
return fmt.Sprintf("%s %s", c.name, c.surname)
}
type EUContact contact
func (euc *EUContact) Birthday() string {
return fmt.Sprintf("%d/%d/%d", euc.birthday.day, euc.birthday.month, euc.birthday.year)
}
type USContact contact
func (usc *USContact) Birthday() string {
return fmt.Sprintf("%d/%d/%d", usc.birthday.month, usc.birthday.day, usc.birthday.year)
}
func main() {
euc := EUContact{
name: "Massimiliano",
surname: "Ziccardi",
birthday: birthday{
day: 14,
month: 6,
year: 1976,
},
}
usc := USContact{
name: "John",
surname: "Doe",
birthday: birthday{
day: 16,
month: 9,
year: 1981,
},
}
fmt.Println(euc.FullName())
fmt.Println(euc.Birthday())
fmt.Println(usc.FullName())
fmt.Println(usc.Birthday())
}
If you try to compile it, you will get another error:
./prog.go:53:18: euc.FullName undefined (type EUContact has no field or method FullName)
./prog.go:56:18: usc.FullName undefined (type USContact has no field or method FullName)
Why does that happen? Before reading on, try to answer.
Let's recap:
contact
structtype EUContact contact
and type USContact contact
EUContact
and USContact
have no FullName
methodIf you look at the declaration for the FullName
method, its receiver is *contact
, but:
EUContact
and USContact
Explanation of the error should be clear now: the method is not defined for EUContact
or USContact
. It is defined only for the contact
object
Does that mean that you have to duplicate FullName
for both EUContact
and USContact
?
While that would work, it wouldn't be a good practice. Luckily enough, Golang provides us with the ability to embed other structs
to achieve what we are trying to do.
To embed a struct
into another struct, simply put the struct name (in our case contact
) into your struct as you would do for a property, but without a name:
package main
import "fmt"
type birthday struct {
day int16
month int16
year int16
}
type contact struct {
name string
surname string
birthday birthday
}
func (c *contact) FullName() string {
return fmt.Sprintf("%s %s", c.name, c.surname)
}
type EUContact struct {
contact
}
func (euc *EUContact) Birthday() string {
return fmt.Sprintf("%d/%d/%d", euc.birthday.day, euc.birthday.month, euc.birthday.year)
}
type USContact struct {
contact
}
func (usc *USContact) Birthday() string {
return fmt.Sprintf("%d/%d/%d", usc.birthday.month, usc.birthday.day, usc.birthday.year)
}
func main() {
euc := EUContact{
name: "Massimiliano",
surname: "Ziccardi",
birthday: birthday{
day: 14,
month: 6,
year: 1976,
},
}
usc := USContact{
name: "John",
surname: "Doe",
birthday: birthday{
day: 16,
month: 9,
year: 1981,
},
}
fmt.Println(euc.FullName())
fmt.Println(euc.Birthday())
fmt.Println(usc.FullName())
fmt.Println(usc.Birthday())
}
Let's try to run it now:
./prog.go:40:3: unknown field 'name' in struct literal of type EUContact
./prog.go:41:3: unknown field 'surname' in struct literal of type EUContact
./prog.go:42:3: unknown field 'birthday' in struct literal of type EUContact
./prog.go:49:3: unknown field 'name' in struct literal of type USContact
./prog.go:50:3: unknown field 'surname' in struct literal of type USContact
./prog.go:51:3: unknown field 'birthday' in struct literal of type USContact
Oops: still errors.
Don't worry: we are almost there. As I said at the beginning of this post, Golang doesn't support inheritance, but we can get something similar through embedding.
While when we access the values of the embedded structs we can freely use the dot notation when we initialise the struct we have to explicitly tell GO that the keys are owned by a specific struct.
To make the code work, change the main
as below:
func main() {
euc := EUContact{
contact{ // (1)
name: "Massimiliano",
surname: "Ziccardi",
birthday: birthday{
day: 14,
month: 6,
year: 1976,
},
},
}
usc := USContact{
contact{ // (2)
name: "John",
surname: "Doe",
birthday: birthday{
day: 16,
month: 9,
year: 1981,
},
},
}
fmt.Println(euc.FullName()) // (3)
fmt.Println(euc.Birthday()) // (4)
fmt.Println(euc.name) // (5)
fmt.Println(usc.FullName()) // (6)
fmt.Println(usc.Birthday()) // (7)
}
--- OUTPUT ---
Massimiliano Ziccardi
14/6/1976
Massimiliano
John Doe
9/16/1981
Finally it works as expected! Let's analyse what we did:
contact
into the contact
scope for EUContact
.USContact
EUContact
object the method inherited from contact
Birthday
for the EUContact
objectname
: note how we don't need to specify that it is part of contact
when using the dot notation.USContact
object the method inherited from contact
Birthday
for the USContact
objectSo we have been able to achieve a kind of inheritance thanks to the type embedding feature of GO!