-
Notifications
You must be signed in to change notification settings - Fork 2
Embedding
Go is not an OOP language in the classical sense. There is no inheritance hierachy, one of the hallmarks of object-oriented languages. Go favors a form of composition known as embedding which allows a struct to include fields and methods of another struct or interface directly.
package main
import (
"fmt"
)
type Person struct {
Name string
Age int
}
func (p Person) Greet() {
fmt.Printf("Hello, my name is %s and I am %d years old.\n", p.Name, p.Age)
}
type Employee struct {
Person
EmployeeID string
}
func (e Employee) ShowEmployeeID() {
fmt.Printf("Employee ID is %s.\n", e.EmployeeID)
}
func main() {
emp := Employee{
Person: Person{
Name: "James Baldwin",
Age: 30,
},
EmployeeID: "E12345",
}
// Access fields and methods from the embedded struct
emp.Greet() // Calls the Greet method from the Person struct
emp.ShowEmployeeID() // Calls the ShowEmployeeID method from the Employee struct
}
package main
import (
"fmt"
)
type Person struct {
Name string
Age int
}
func (p Person) Greet() {
fmt.Printf("Hello, my name is %s and I am %d years old.\n", p.Name, p.Age)
}
type Employee struct {
Person
Company string
EmployeeID string
}
// Override the Greet method in the Employee struct
func (e Employee) Greet() {
fmt.Printf("Hello, my name is %s and I work at %s with ID %s.\n", e.Name, e.Company, e.EmployeeID)
}
func main() {
emp := Employee{
Person: Person{
Name: "Jane Doe",
Age: 28,
},
Company: "Acme Corp",
EmployeeID: "E67890",
}
// Access overridden method
emp.Greet() // Calls the Greet method from the Employee struct
}
When you embed multiple structs that have methods with the same name, Go will not allow you to directly call the ambiguous method without specifying which embedded struct to use.
package main
import (
"fmt"
)
type Person struct {
Name string
}
func (p Person) Show() {
fmt.Printf("Person: %s\n", p.Name)
}
type Contact struct {
Phone string
}
func (c Contact) Show() {
fmt.Printf("Phone: %s\n", c.Phone)
}
type Employee struct {
Person
Contact
}
func main() {
emp := Employee{
Person: Person{Name: "John Doe"},
Contact: Contact{Phone: "123-456-7890"},
}
// This will cause a compile-time error due to ambiguity
emp.Show()
// You need to specify which Show method to call
emp.Person.Show()
emp.Contact.Show()
}
If the embedding struct and the embedded struct have fields with the same name, the field in the embedding struct will shadow the field in the embedded struct.
package main
import (
"fmt"
)
type Person struct {
Name string
}
type Employee struct {
Person
Name string // This shadows Person's Name
}
func main() {
emp := Employee{
Person: Person{Name: "John Doe"},
Name: "Jane Doe",
}
// Accessing the shadowed field directly
fmt.Println(emp.Name) // Outputs: Jane Doe
fmt.Println(emp.Person.Name) // Outputs: John Doe
}
Interface embedding provides a powerful mechanism to combine behaviors.
package main
import (
"fmt"
)
type Reader interface {
Read() string
}
type Writer interface {
Write(data string)
}
type Closer interface {
Close()
}
type ReadWriterCloser interface {
Reader
Writer
Closer
}
type File struct {
data string
closed bool
}
func (f *File) Read() string {
if f.closed {
return "Cannot read, file is closed."
}
return f.data
}
func (f *File) Write(data string) {
if f.closed {
fmt.Println("Cannot write, file is closed.")
return
}
f.data = data
}
func (f *File) Close() {
f.closed = true
fmt.Println("File closed.")
}
func main() {
var device ReadWriterCloser = &File{}
device.Write("Hello, Gophers!")
fmt.Println(device.Read()) // Outputs: Hello, Gophers!
device.Close()
device.Write("Trying to write after closing")
fmt.Println(device.Read()) // Outputs: Cannot read, file is closed.
}
Though not exactly the same, does the above remind you of anything from the standard library itself? See ReadWriteCloser.
Caution: If two embedded interfaces have methods with the same name and signature, the resulting interface will include only one of those methods. This can lead to unexpected behavior if you expect both methods to be available separately.
package main
import "fmt"
type Reader interface {
Read() string
}
type ReadingWriter interface {
Write(data string)
Read() string
}
type ReadWriter interface {
Reader
ReadingWriter
}
type MyType struct{}
func (ms MyType) Read() string {
return "Reading data"
}
func (ms MyType) Write(data string) {
fmt.Println("Writing data:", data)
}
func main() {
var rw ReadWriter = MyType{}
// Only one Read method is available, not two separate Read methods
fmt.Println(rw.Read()) // Outputs: Reading data
rw.Write("some data") // Outputs: Writing data: some data
}