메서드 프로모션
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 36번째.
구조체 임베딩이 필드를 외부 구조체로 승격시키는 것과 마찬가지로, Go는 임베디드 타입의 메서드도 승격시킵니다. 이는 임베디드 구조체에 정의된 메서드를 외부 구조체에서 직접 호출할 수 있음을 의미합니다.
type Engine struct {
Horsepower int
}
func (e Engine) Start() string {
return "Engine started"
}
type Car struct {
Model string
Engine // 임베디드됨
}
Car가 Engine을 임베딩하므로, Start() 메서드가 자동으로 승격됩니다:
func main() {
c := Car{
Model: "Sedan",
Engine: Engine{Horsepower: 200},
}
fmt.Println(c.Start()) // 엔진이 시작됨
fmt.Println(c.Engine.Start()) // 이것도 작동합니다
}
이러한 승격(promotion)은 인터페이스에 중요한 결과를 가져옵니다. 임베디드 타입이 인터페이스를 충족하면, 외부 타입도 자동으로 해당 인터페이스를 충족하게 됩니다:
type Starter interface {
Start() string
}
func Ignite(s Starter) {
fmt.Println(s.Start())
}
func main() {
c := Car{Model: "Sedan", Engine: Engine{Horsepower: 200}}
Ignite(c) // Car는 Engine을 통해 Starter를 충족합니다
}
Car 타입은 Starter를 명시적으로 구현하지 않지만, 임베디드된 Engine이 필요한 메서드를 가지고 있기 때문에 인터페이스를 충족합니다. 이것이 Go가 전통적인 상속 없이 동작 재사용을 달성하는 방법입니다.
챌린지
쉬움임베디드 타입의 메서드가 외부 타입으로 어떻게 승격(promotion)되는지, 그리고 이것이 구성을 통해 인터페이스 만족을 어떻게 가능하게 하는지 보여주는 음악 플레이어 시스템을 구축해 보겠습니다.
코드는 두 개의 파일로 구성됩니다:
audio.go: 음악 플레이어를 위한 구성 요소를 생성합니다:Brand필드(string)와"[Brand] playing audio"를 반환하는Play() string메서드를 가진AudioPlayer구조체Play() string메서드를 요구하는Playable인터페이스AudioPlayer를 임베드하고Model필드를 가진Smartphone구조체AudioPlayer를 임베드하고Model필드를 가진Tablet구조체
main.go: 임의의Playable을 인자로 받아Play()호출 결과를 반환하는StartPlayback함수를 생성합니다. 입력에서 기기 정보를 읽어 Smartphone과 Tablet을 모두 생성하고, 두 타입 모두Play()를 직접 구현하지 않았음에도 임베드된AudioPlayer를 통해Playable인터페이스를 만족함을 증명합니다.
다음 입력이 제공됩니다:
- 1행: 스마트폰 모델 이름
- 2행: 스마트폰 오디오 브랜드
- 3행: 태블릿 모델 이름
- 4행: 태블릿 오디오 브랜드
각 기기에 대해 모델 이름을 출력한 다음, 해당 기기를 StartPlayback에 전달한 결과를 출력하세요.
예를 들어, iPhone 15, Apple Audio, iPad Pro, Beats가 주어지면 출력은 다음과 같아야 합니다:
iPhone 15
Apple Audio playing audio
iPad Pro
Beats playing audioAudioPlayer의 Play() 메서드가 각 외부 타입으로 자동으로 승격되어, 추가 코드 없이도 Smartphone과 Tablet 모두 StartPlayback에 전달되어 Playable 인터페이스를 만족하는 방식에 주목하세요.
치트 시트
구조체가 다른 타입을 임베딩(embedding)하면, 임베딩된 타입의 메서드는 자동으로 외부 구조체로 승격(promote)됩니다:
type Engine struct {
Horsepower int
}
func (e Engine) Start() string {
return "Engine started"
}
type Car struct {
Model string
Engine // 임베딩됨
}
func main() {
c := Car{
Model: "Sedan",
Engine: Engine{Horsepower: 200},
}
fmt.Println(c.Start()) // Engine started
fmt.Println(c.Engine.Start()) // 역시 작동합니다
}
임베딩된 타입이 인터페이스를 충족하면, 외부 타입도 자동으로 해당 인터페이스를 충족하게 됩니다:
type Starter interface {
Start() string
}
func Ignite(s Starter) {
fmt.Println(s.Start())
}
func main() {
c := Car{Model: "Sedan", Engine: Engine{Horsepower: 200}}
Ignite(c) // Car는 Engine을 통해 Starter를 충족합니다
}
이를 통해 전통적인 상속 없이도 구성을 통한 동작 재사용이 가능해집니다.
직접 해보기
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: 임의의 Playable을 수락하고
// Play() 호출 결과를 반환하는 StartPlayback 함수를 생성하세요.
func main() {
scanner := bufio.NewScanner(os.Stdin)
// 스마트폰 모델 읽기
scanner.Scan()
smartphoneModel := scanner.Text()
// 스마트폰 오디오 브랜드 읽기
scanner.Scan()
smartphoneBrand := scanner.Text()
// 태블릿 모델 읽기
scanner.Scan()
tabletModel := scanner.Text()
// 태블릿 오디오 브랜드 읽기
scanner.Scan()
tabletBrand := scanner.Text()
// TODO: AudioPlayer가 포함된 Smartphone을 생성하세요.
// TODO: AudioPlayer가 포함된 Tablet을 생성하세요.
// TODO: 스마트폰 모델을 출력한 다음, 스마트폰으로 StartPlayback을 호출하세요.
// TODO: 태블릿 모델을 출력한 다음, 태블릿으로 StartPlayback을 호출하세요.
_ = smartphoneModel
_ = smartphoneBrand
_ = tabletModel
_ = tabletBrand
fmt.Println("TODO: Complete the implementation")
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서