Go에서의 덕 타이핑
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 42번째.
덕 타이핑(duck typing)이라는 용어는 "만약 그것이 오리처럼 걷고 오리처럼 꽥꽥거린다면, 그것은 오리다"라는 속담에서 유래되었습니다. Go에서 이는 타입이 인터페이스를 구현한다고 명시적으로 선언할 필요가 없음을 의미합니다. 단지 적절한 메서드들을 가지고 있기만 하면 됩니다.
다음 인터페이스와 서로 전혀 관련이 없는 두 가지 타입을 살펴보겠습니다:
type Quacker interface {
Quack() string
}
type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }
type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }
Duck이나 Robot 모두 정의 어디에서도 Quacker를 언급하지 않습니다. 하지만 두 타입 모두 올바른 시그니처를 가진 Quack() 메서드를 가지고 있다는 것만으로 인터페이스를 충족합니다:
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
이러한 암시적 충족(implicit satisfaction)은 타입이 이미 존재한 후에 인터페이스를 정의할 수 있게 해주기 때문에 매우 강력합니다. 외부 패키지의 소스 코드를 수정하지 않고도 해당 패키지의 타입과 일치하는 인터페이스를 생성할 수 있습니다. 타입은 인터페이스에 대해 알 필요가 없으며, 그저 올바르게 동작하기만 하면 됩니다.
Go에서의 덕 타이핑(Duck typing)은 컴파일 타임 타입 안정성을 유지하면서도 동적 언어의 유연성을 제공합니다. 컴파일러는 타입이 인터페이스 값으로 사용되기 전에 실제로 필요한 메서드를 가지고 있는지 확인합니다.
챌린지
쉬움덕 타이핑(duck typing)이 실제로 어떻게 작동하는지 보여주는 메시징 시스템을 구축해 보겠습니다. 서로 완전히 관련이 없는 타입들이 공통 인터페이스를 명시적으로 구현한다고 선언하지 않고도, 해당 인터페이스를 통해 메시지를 보낼 수 있도록 만들 것입니다.
코드는 세 개의 파일로 구성됩니다:
messengers.go: 동일한 동작을 공유하는 서로 관련 없는 세 개의 구조체를 생성합니다:Number필드를 가진Phone—이 구조체의SendMessage(text string) string메서드는"SMS to [Number]: [text]"를 반환합니다.Email필드를 가진Computer—이 구조체의SendMessage(text string) string메서드는"Email to [Email]: [text]"를 반환합니다.Name필드를 가진Pigeon—이 구조체의SendMessage(text string) string메서드는"[Name] carries: [text]"를 반환합니다.
sender.go:SendMessage(text string) string메서드를 요구하는Messenger인터페이스를 정의합니다.Messenger슬라이스와 메시지 문자열을 인자로 받는Broadcast함수를 만듭니다. 이 함수는 각 메신저에서SendMessage를 호출한 결과를 담은 문자열 슬라이스를 반환해야 합니다.main.go: 입력에서 세부 정보를 읽고, 각 메신저 타입의 인스턴스를 하나씩 생성한 다음, 이들을Messenger슬라이스에 수집합니다. 그런 다음Broadcast를 사용하여 모든 메신저를 통해 메시지를 전송하고, 각 결과를 개별 줄에 출력합니다.
다음과 같은 입력이 제공됩니다:
- 1행: 전화번호
- 2행: 이메일 주소
- 3행: 비둘기 이름
- 4행: 방송할 메시지
예를 들어, 555-1234, alice@mail.com, Winston, Hello World가 주어지면 출력은 다음과 같아야 합니다:
SMS to 555-1234: Hello World
Email to alice@mail.com: Hello World
Winston carries: Hello World여기서 핵심 통찰은 Phone, Computer, Pigeon이 공통점이 전혀 없다는 것입니다. 이들은 기본 타입을 공유하지도 않고 Messenger를 명시적으로 구현하지도 않습니다. 하지만 모두 똑같이 "꽥꽥"거리기 때문에(일치하는 메서드를 가지고 있기 때문에), Go는 이들 모두를 Messenger 값으로 사용할 수 있게 해줍니다. 이것이 바로 작동 중인 덕 타이핑입니다.
치트 시트
Go에서, 덕 타이핑(duck typing)은 타입이 인터페이스를 구현한다고 명시적으로 선언할 필요가 없음을 의미합니다. 단지 일치하는 시그니처를 가진 필요한 메서드들을 가지고 있기만 하면 됩니다.
타입이 인터페이스가 요구하는 모든 메서드를 가지고 있다면, 해당 인터페이스를 암시적으로 충족합니다:
type Quacker interface {
Quack() string
}
type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }
type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }
Duck과 Robot 모두 정의 부분에서 언급하지 않고도 Quacker 인터페이스를 충족합니다:
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
이러한 암시적 충족 방식 덕분에 타입이 이미 존재한 후에 인터페이스를 정의할 수 있으며, 외부 패키지의 소스 코드를 수정하지 않고도 해당 타입과 일치하는 인터페이스를 만들 수 있습니다. 컴파일러는 컴파일 시점에 타입이 필요한 메서드를 가지고 있는지 확인하여, 유연성을 유지하면서도 타입 안전성을 제공합니다.
직접 해보기
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// 전화번호 읽기
scanner.Scan()
phoneNumber := scanner.Text()
// 이메일 주소 읽기
scanner.Scan()
email := scanner.Text()
// 비둘기 이름 읽기
scanner.Scan()
pigeonName := scanner.Text()
// 방송할 메시지 읽기
scanner.Scan()
message := scanner.Text()
// TODO: 각 메신저 유형(Phone, Computer, Pigeon) 중 하나씩 생성
// TODO: 모든 메신저를 Messenger 슬라이스에 수집
// TODO: Broadcast를 사용하여 모든 메신저를 통해 메시지 전송
// TODO: 각 결과를 별도의 줄에 출력
// 변수 사용을 위한 자리 표시자 (구현 시 제거)
_ = phoneNumber
_ = email
_ = pigeonName
_ = message
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서