Replacing an inherited behaviour
A Puppy is a kind of Dog, but it sounds different: "Yip!" instead of "Woof!". When a child type needs to change a method it got from its parent, other languages call this overriding. Go has no override keyword and no virtual keyword. What it has is shadowing: if the outer type declares a method with the same name as one promoted from an embedded type, the outer method wins.
type Dog struct{}
func (d Dog) bark() { fmt.Println("Woof!") }
type Puppy struct{ Dog }
func (p Puppy) bark() { fmt.Println("Yip!") }
Now Puppy{}.bark() prints Yip!. Go looks for bark on Puppy first, finds it there, and never reaches the promoted one.
The parent's method is still there
Shadowing hides the embedded method behind a longer name; it does not delete it. The full path reaches the original, which is how a child extends a parent method instead of replacing it:
func (p Puppy) bark() {
p.Dog.bark() // run Dog's version first
fmt.Println("...and a yip!")
}
This takes the place of the super.bark() call known from other languages.
A real difference: no dynamic dispatch through the parent
In languages with true overriding, a method defined in the parent that calls bark() runs the child's version when the object is really a child. Embedding does not work that way. The embedded Dog knows nothing about the Puppy around it, so its methods always call Dog's own methods. This program confirms it:
type Dog struct{ name string }
func (this Dog) bark() { fmt.Printf("Woof! I am %s\n", this.name) }
func (d Dog) intro() { d.bark() }
type Puppy struct{ Dog }
func (this Puppy) bark() { fmt.Printf("Yip! I am %s\n", this.name) }
func main() {
p := Puppy{Dog: Dog{name: "Buddy"}}
p.bark() // Yip! I am Buddy
p.Dog.bark() // Woof! I am Buddy
p.intro() // Woof! I am Buddy
}
The last line shows it: intro is promoted from Dog and calls d.bark() on the embedded dog, so it prints the Dog version even when called through a puppy. When you need the child's behaviour to be chosen at run time, use an interface, as the next lesson does.
Fields shadow too
The same rule applies to fields. If Puppy declared its own field called name, then p.name would mean the puppy's field, and the dog's would be reachable only as p.Dog.name. The shallower name always wins, so the depth at which a name is declared decides which one a selector finds.
Signatures need not match
The outer method only has to share the name. It can take different parameters or return other types, because it simply hides the promoted one. Nothing checks that the two agree, so a typo in the name quietly creates a new method instead of replacing the old one. Compare this with the compiler-checked override markers of other languages.
The lesson's exercise
The starter code has Dog with bark, Puppy embedding it, a NewPuppy constructor and a mainFunction that creates "Buddy" and calls bark(). Only the new method is missing.
- Declare
barkonPuppy, belowNewPuppy, with the receiver(this Puppy). It shadows the method promoted fromDog. - Print the message with
fmt.Printf:Yip! I amand the name, using%sand a trailing\n. Herethis.namereaches the embedded dog's field directly.
type Dog struct {
name string
}
func (this Dog) bark() {
fmt.Printf("Woof! I am %s\n", this.name)
}
type Puppy struct {
Dog
}
func NewPuppy(name string) Puppy {
return Puppy{Dog: Dog{name: name}}
}
func (this Puppy) bark() {
fmt.Printf("Yip! I am %s\n", this.name)
}
func mainFunction() {
buddy := NewPuppy("Buddy")
buddy.bark()
}
It prints:
Yip! I am Buddy
Common mistakes
- Misspelling the name.
Barkandbarkare different methods in Go, sofunc (this Puppy) Bark()does not shadowbark, and the call still prints the Woof line. - Using the wrong receiver. A method on
Dogcannot replace the promoted one. The new method needs thePuppyreceiver. - Expecting the parent to call the child's version. As shown above, methods promoted from
Dogkeep callingDog's methods. - Calling the parent's method by accident from inside the override. Writing
this.bark()insidePuppy'sbarkcalls itself forever. Usethis.Dog.bark()to reach the original.
Key takeaways
- Declare a method with the same name on the outer type to replace a promoted one.
- The original stays reachable through the embedded field, such as
p.Dog.bark(). - Embedded types never call back into the outer type's methods.
- For run-time choice between behaviours, use interfaces.