Menu
Coddy logo textTech

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 icon

Challenge

Easy

Let'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 a BaseNotification struct with Title and Priority fields (both strings). Give it a Format() string method that returns the notification in a standard format: [Priority] Title.
  • channels.go: Create two notification channel types that embed BaseNotification and shadow the Format method with their own implementations:
    • EmailNotification with a Recipient field—its Format() should return Email to [Recipient]: [Priority] Title
    • SlackNotification with a Channel field—its Format() should call the embedded BaseNotification.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:
    1. The EmailNotification's formatted output (using its shadowed method)
    2. The EmailNotification's base format (explicitly calling the embedded method)
    3. The SlackNotification's formatted output (which internally uses the base method)

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 Alert

Notice 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
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming