여러 구조체 임베딩하기
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 37번째.
Go에서는 단일 구조체 내에 여러 구조체를 임베드하여 다양한 소스의 동작을 결합할 수 있습니다. 이는 다른 언어의 다중 상속과 유사하지만, Go의 구성(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
}
Service 구조체는 이제 두 임베디드 타입의 메서드에 모두 접근할 수 있습니다:
func main() {
s := Service{Name: "OrderService"}
fmt.Println(s.Log("started")) // LOG: started
fmt.Println(s.Notify("new order")) // NOTIFY: new order
}
여러 구조체를 임베딩할 때, 두 개의 임베딩된 타입이 동일한 이름의 메서드를 가지고 있으면 이름 충돌이 발생할 수 있습니다. Go는 이러한 모호성을 자동으로 해결하지 않습니다:
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() - 컴파일 에러: 모호한 선택자(ambiguous selector)
fmt.Println(c.A.Greet()) // Hello from A
fmt.Println(c.B.Greet()) // Hello from B
}
충돌이 발생할 때, 타입 이름을 한정자로 사용하여 호출하려는 임베디드 타입의 메서드를 명시적으로 지정해야 합니다.
챌린지
쉬움구조체 임베딩(struct embedding)을 통해 여러 기능을 결합한 스마트 홈 장치 시스템을 구축해 보겠습니다. 서로 다른 컨트롤러 타입을 임베딩하여 조명을 제어하고 음악을 재생할 수 있는 장치를 만들 것입니다.
코드는 다음 세 개의 파일로 구성됩니다:
controllers.go: 서로 다른 기능을 제공하는 두 개의 독립적인 컨트롤러 구조체를 생성합니다:Brightness필드(int)와Light set to [level]%를 반환하는SetLight(level int) string메서드를 가진LightControllerVolume필드(int)와Volume set to [level]%를 반환하는SetVolume(level int) string메서드를 가진AudioController
Status() string메서드를 가져야 합니다. 이로 인해 발생하는 이름 충돌을 처리해야 합니다.LightController.Status()는Brightness: [Brightness]%를 반환해야 하고,AudioController.Status()는Volume: [Volume]%를 반환해야 합니다.device.go:Name필드를 가지고LightController와AudioController를 모두 임베딩하는SmartDevice구조체를 생성합니다. 각 임베딩된 타입의Status()메서드를 명시적으로 호출하여 모호성을 해결하고, 장치 이름과 두 컨트롤러의 상태를 함께 반환하는FullStatus() string메서드를 추가합니다.main.go: 입력으로부터 장치 설정을 읽고,SmartDevice를 생성한 뒤, 승격된(promoted) 메서드를 사용하여 조명과 볼륨 레벨을 설정하고 전체 상태를 표시합니다.
다음 입력이 제공됩니다:
- 1행: 장치 이름
- 2행: 초기 밝기 레벨 (정수)
- 3행: 초기 볼륨 레벨 (정수)
- 4행: 새로 설정할 밝기 레벨 (정수)
- 5행: 새로 설정할 볼륨 레벨 (정수)
FullStatus() 메서드는 다음을 반환해야 합니다:
[Name] - [LightController.Status()], [AudioController.Status()]임베딩된 타입에서 승격된 SetLight와 SetVolume 호출 결과를 출력한 다음, 전체 상태를 출력하세요.
예를 들어, Living Room Hub, 50, 30, 75, 60이 주어지면 출력은 다음과 같아야 합니다:
Light set to 75%
Volume set to 60%
Living Room Hub - Brightness: 75%, Volume: 60%SetLight와 SetVolume은 메서드 승격을 통해 SmartDevice에서 직접 접근할 수 있지만, Status()는 두 임베딩된 타입 모두 해당 메서드를 가지고 있기 때문에 명시적인 지정이 필요하다는 점에 유의하세요.
치트 시트
Go는 단일 구조체 내에 여러 구조체를 임베딩하여 서로 다른 소스의 동작을 구성을 통해 결합할 수 있도록 합니다:
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
}
Service 구조체는 임베딩된 두 타입의 메서드에 모두 접근할 수 있습니다:
s := Service{Name: "OrderService"}
fmt.Println(s.Log("started")) // LOG: started
fmt.Println(s.Notify("new order")) // NOTIFY: new order
임베딩된 타입들이 동일한 이름의 메서드를 가지고 있을 때 이름 충돌(Naming conflicts)이 발생합니다. 이 경우 어떤 임베딩된 타입의 메서드를 호출할지 명시적으로 지정해야 합니다:
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
직접 해보기
package main
import (
"fmt"
)
func main() {
// 입력 읽기
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: 주어진 이름과 초기값을 사용하여 SmartDevice 생성
// TODO: 승계된 SetLight 메서드를 사용하고 결과 출력
// TODO: 승계된 SetVolume 메서드를 사용하고 결과 출력
// TODO: FullStatus() 메서드를 사용하여 전체 상태 출력
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서