Embedding Multiple Structs
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 37 of 107.
Go allows you to embed multiple structs within a single struct, combining behaviors from different sources. This is similar to multiple inheritance in other languages, but with Go's composition approach.
type Logger struct{}
func (l Logger) Log(msg string) string {
return "LOG: " + msg
}
type Notifier struct{}
func (n Notifier) Notify(msg string) string {
return "NOTIFY: " + msg
}
type Service struct {
Name string
Logger
Notifier
}
The Service struct now has access to methods from both embedded types:
func main() {
s := Service{Name: "OrderService"}
fmt.Println(s.Log("started")) // LOG: started
fmt.Println(s.Notify("new order")) // NOTIFY: new order
}
When embedding multiple structs, a naming conflict can occur if two embedded types have methods with the same name. Go doesn't automatically resolve this ambiguity:
type A struct{}
func (A) Greet() string { return "Hello from A" }
type B struct{}
func (B) Greet() string { return "Hello from B" }
type Combined struct {
A
B
}
func main() {
c := Combined{}
// c.Greet() - compile error: ambiguous selector
fmt.Println(c.A.Greet()) // Hello from A
fmt.Println(c.B.Greet()) // Hello from B
}
When conflicts arise, you must explicitly specify which embedded type's method you want to call using the type name as a qualifier.
Challenge
EasyLet's build a smart home device system that combines multiple capabilities through struct embedding. You'll create a device that can both control lighting and play music by embedding separate controller types.
You'll organize your code across three files:
controllers.go: Create two independent controller structs that provide different capabilities:- A
LightControllerwith aBrightnessfield (int) and aSetLight(level int) stringmethod that returnsLight set to [level]% - An
AudioControllerwith aVolumefield (int) and aSetVolume(level int) stringmethod that returnsVolume set to [level]%
Status() stringmethod—this creates a naming conflict you'll need to handle.LightController.Status()should returnBrightness: [Brightness]%andAudioController.Status()should returnVolume: [Volume]%.- A
device.go: Create aSmartDevicestruct with aNamefield that embeds bothLightControllerandAudioController. Add a method calledFullStatus() stringthat returns the device name along with both controller statuses, resolving the ambiguity by explicitly calling each embedded type'sStatus()method.main.go: Read device configuration from input, create aSmartDevice, use the promoted methods to set light and volume levels, then display the full status.
The following inputs will be provided:
- Line 1: Device name
- Line 2: Initial brightness level (integer)
- Line 3: Initial volume level (integer)
- Line 4: New brightness level to set (integer)
- Line 5: New volume level to set (integer)
Your FullStatus() method should return:
[Name] - [LightController.Status()], [AudioController.Status()]Print the results of calling SetLight and SetVolume (which are promoted from the embedded types), then print the full status.
For example, given Living Room Hub, 50, 30, 75, and 60, your output should be:
Light set to 75%
Volume set to 60%
Living Room Hub - Brightness: 75%, Volume: 60%Notice how SetLight and SetVolume are directly accessible on SmartDevice through method promotion, but Status() requires explicit qualification because both embedded types have that method.
Cheat sheet
Go allows embedding multiple structs within a single struct, combining behaviors from different sources through composition:
type Logger struct{}
func (l Logger) Log(msg string) string {
return "LOG: " + msg
}
type Notifier struct{}
func (n Notifier) Notify(msg string) string {
return "NOTIFY: " + msg
}
type Service struct {
Name string
Logger
Notifier
}
The Service struct has access to methods from both embedded types:
s := Service{Name: "OrderService"}
fmt.Println(s.Log("started")) // LOG: started
fmt.Println(s.Notify("new order")) // NOTIFY: new order
Naming conflicts occur when embedded types have methods with the same name. You must explicitly specify which embedded type's method to call:
type A struct{}
func (A) Greet() string { return "Hello from A" }
type B struct{}
func (B) Greet() string { return "Hello from B" }
type Combined struct {
A
B
}
// c.Greet() - compile error: ambiguous selector
c := Combined{}
fmt.Println(c.A.Greet()) // Hello from A
fmt.Println(c.B.Greet()) // Hello from B
Try it yourself
package main
import (
"fmt"
)
func main() {
// Read input
var name string
fmt.Scanln(&name)
var initialBrightness int
fmt.Scanln(&initialBrightness)
var initialVolume int
fmt.Scanln(&initialVolume)
var newBrightness int
fmt.Scanln(&newBrightness)
var newVolume int
fmt.Scanln(&newVolume)
// TODO: Create a SmartDevice with the given name and initial values
// TODO: Use the promoted SetLight method and print the result
// TODO: Use the promoted SetVolume method and print the result
// TODO: Print the full status using FullStatus() method
}
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