Shadowing Embedded Methods
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 39 of 107.
Sometimes you need the outer struct to provide its own implementation of a method that the embedded type already has. This is called shadowing, and it lets you override promoted methods with custom behavior.
When you define a method on the outer struct with the same name as an embedded method, the outer method takes precedence:
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return "Some sound"
}
type Dog struct {
Animal
Breed string
}
func (d Dog) Speak() string {
return "Woof!"
}
Now when you call Speak() on a Dog, the outer method is used:
func main() {
d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Labrador"}
fmt.Println(d.Speak()) // Woof! - Dog's method
fmt.Println(d.Animal.Speak()) // Some sound - Animal's method
}
The embedded method isn't gone. You can still access it explicitly through the embedded type name. This pattern is useful when you want to customize behavior while keeping the original implementation available, perhaps to call it from within your overriding method:
func (d Dog) Speak() string {
return d.Animal.Speak() + " (but actually Woof!)"
}
Shadowing gives you control over which behavior is exposed while maintaining access to the original through explicit qualification.
Challenge
EasyLet's build a notification system where different notification channels can customize how they format and deliver messages. You'll use method shadowing to let specific channels override default behavior while still being able to access the original implementation when needed.
You'll organize your code across three files:
base.go: Create aBaseNotificationstruct withTitleandPriorityfields (both strings). Give it aFormat() stringmethod that returns the notification in a standard format:[Priority] Title.channels.go: Create two notification channel types that embedBaseNotificationand shadow theFormatmethod with their own implementations:EmailNotificationwith aRecipientfield—itsFormat()should returnEmail to [Recipient]: [Priority] TitleSlackNotificationwith aChannelfield—itsFormat()should call the embeddedBaseNotification.Format()method and prepend#[Channel]:to the result
main.go: Read notification details from input, create both types of notifications, and demonstrate shadowing by printing:- The
EmailNotification's formatted output (using its shadowed method) - The
EmailNotification's base format (explicitly calling the embedded method) - The
SlackNotification's formatted output (which internally uses the base method)
- The
The following inputs will be provided:
- Line 1: Notification title
- Line 2: Priority level
- Line 3: Email recipient
- Line 4: Slack channel name
For example, given Server Alert, HIGH, admin@company.com, and ops-alerts, your output should be:
Email to admin@company.com: HIGH Server Alert
[HIGH] Server Alert
#ops-alerts: [HIGH] Server AlertNotice how EmailNotification completely replaces the base formatting, while SlackNotification enhances it by calling the original method and adding channel context. Both approaches demonstrate different ways to use method shadowing effectively.
Cheat sheet
When you define a method on an outer struct with the same name as a method from an embedded type, the outer method shadows (overrides) the embedded method:
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return "Some sound"
}
type Dog struct {
Animal
Breed string
}
func (d Dog) Speak() string {
return "Woof!"
}
When calling the method on the outer struct, the outer method takes precedence:
d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Labrador"}
fmt.Println(d.Speak()) // Woof! - Dog's method
fmt.Println(d.Animal.Speak()) // Some sound - Animal's method
The embedded method remains accessible through explicit qualification using the embedded type name. You can call the original implementation from within the overriding method:
func (d Dog) Speak() string {
return d.Animal.Speak() + " (but actually Woof!)"
}
Try it yourself
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Read notification title
scanner.Scan()
title := scanner.Text()
// Read priority level
scanner.Scan()
priority := scanner.Text()
// Read email recipient
scanner.Scan()
recipient := scanner.Text()
// Read Slack channel name
scanner.Scan()
channel := scanner.Text()
// TODO: Create an EmailNotification with the base notification, title, priority, and recipient
// TODO: Create a SlackNotification with the base notification, title, priority, and channel
// TODO: Print the EmailNotification's formatted output (using its shadowed method)
// TODO: Print the EmailNotification's base format (explicitly calling the embedded method)
// TODO: Print the SlackNotification's formatted output
fmt.Println() // Placeholder - remove when implementing
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool