Go supports defining is-a relationships using an embedded type.
Fields and methods in a struct have a has-a relationship
E.g. Person has-a name and email.
type Person struct {
Name string
Email string
}
Embedding to define an is-a relationships is declared with a type but no explicit field name. The unqualified type name acts as the field name.
type Employee struct {
Person
EmployeeID string
}
This way the Person
struct can be accessed using the type name:
accountant := new(Employee)
accountant.Person.Pay()
We can also invoke any Person
methods directly on the Employee object
accountant := new(Employee)
accountant.Pay()
We cannot embed a slice or a map
type Employee struct {
[]Person
EmployeeID string
}
This will give a syntax error: unexpected [, expecting field name or embedded type.
A workaround will be defining a type and then embedding :
type SpecialPeople []Person
type Employee struct {
SpecialPeople
EmployeeID string
}
The spec in Go describes embedding as below:
EmbeddedField = [ "*" ] TypeName .
Embed by-pointer
The advantage of embedding by reference is that you are embedding all the functionality of a type without needing to know when it is instantiated. The major application of this would be to have thousands of instances sharing a single underlying data structure. This can significantly reduce memory consumption.
type Image struct {
data [5][5]
}
type Block struct {
*Image
show bool
}
Because, the unqualified type name acts as the field name for an embedded struct, we can’t have an embedded struct and its pointer in the same struct.
type Image struct {
T // conflicts with embedded field *T and *P.T
*T // conflicts with embedded field T and *P.T
*P.T // conflicts with embedded field T and *T
}
Promotion of fields or methods
All field or method calls for embedded type objects are resolved at compile-time without the use of a virtual table. A field or method of an embedded field in a struct is called promoted.
type Person struct {
Name string
Email string
}
func (p *Person) FreeGift() int64{
return 100
}
type Employee struct {
Person
EmployeeID string
}
In this example person.FreeGift() and employee.FreeGift() is the same so the method is promoted. Promotion occurs only at the first level.
https://play.golang.org/p/N8rXyIR_CW
However, if we have a method with the same name defined on the parent struct as below, then employee.FreeGift() will invoke the method on the Employee struct.
func (e *Employee) FreeGift() int64{
return 200
}
In the above example, calling person.FreeGift() and employee.Person.FreeGift() will invoke the method on the Person struct
https://play.golang.org/p/lYUTijVF7U
One very important design aspect to remember is, if you plan to use an embedded struct and the embedded type has non-exported fields or methods, those are completely inaccessible to you in a separate package.
Embedding Interfaces
Embedding an interface will add all (exported and non-exported) methods of the embedded interface to the enclosing interface.
type ImageWriter interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type ImageFile interface {
ImageWriter // same as adding methods of ImageWriter
Close()
}
Check out this example to add logging to your struct by embedding the log.Logger object.