Go에 상속이 없는 이유
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 107개 중 34번째.
Java와 C++ 같은 전통적인 객체 지향 언어는 타입 간에 코드를 공유하기 위해 상속을 사용합니다. 자식 클래스는 부모 클래스를 확장하여 부모 클래스의 모든 필드와 메서드를 상속합니다. Go는 의도적으로 이 기능을 제외했습니다.
상속은 타입 간에 강한 결합을 만듭니다. 부모 클래스가 변경되면 모든 자식 클래스가 영향을 받습니다.
깊은 상속 계층 구조는 이해하고 유지 관리하기가 어려워집니다. "취약한 기본 클래스 문제"는 기본 클래스의 수정이 파생 클래스를 예기치 않게 손상시킬 때 발생합니다.
Go의 설계자들은 다른 길을 선택했습니다. 바로 상속보다 합성입니다. "개는 동물이다"라고 말하는 대신, Go는 "개는 동물과 같은 동작을 has한다"고 말하도록 권장합니다. 이러한 미묘한 변화는 더 유연하고 유지 관리하기 쉬운 코드를 만들어 냅니다.
Go는 이미 배운 두 가지 메커니즘을 통해 코드를 재사용합니다:
- 인터페이스는 구현 세부 정보 없이 동작 계약을 정의합니다
- 구조체 임베딩을 사용하면 타입에 다른 타입을 포함하고 해당 메서드를 재사용할 수 있습니다.
이 비교를 고려해 보세요. 전통적인 OOP에서는 class Dog extends Animal과 같이 작성할 수 있습니다. Go에서는 Animal 구조체를 Dog 안에 임베드하고 공유 interface를 구현합니다. 그 결과 type 간의 결합도는 더 느슨하면서도 유사한 functionality를 얻습니다.
이 장에서는 구조체 임베딩을 심층적으로 살펴보며, Go가 상속의 단점 없이 그 이점을 어떻게 실현하는지 보여 줍니다.
챌린지
쉬움상속 대신 Go의 composition 접근 방식을 보여 주는 notification 시스템을 만들어 보겠습니다. class 계층 구조가 아니라 interface와 struct embedding을 통해 동작을 공유하는 타입을 만들게 됩니다.
코드를 세 개의 파일로 구성합니다:
notifier.go: 하나의 methodNotify(message string) string을 포함하는Notifierinterface를 Define합니다. 또한 다른 타입에 embedded될Namefield를 가진BaseNotifierstruct를 만듭니다.BaseNotifier에Format(message string) string이라는 method를 추가하여, 대괄호 안에 notifier의 이름을 앞에 붙인 message를 반환하도록 합니다.channels.go:BaseNotifier를 embedded하고Notifierinterface를 Implement하는 두 가지 notification channel 타입을 만듭니다:EmailNotifier: 추가Addressfield 포함SMSNotifier: 추가Phonefield 포함
Notifymethod는 embedded된Formatmethod를 사용하고, 해당 channel의 고유한 정보를 출력에 포함해야 합니다.main.go: 모든Notifier와 message를 accepts하고,Notify를 calling한 결과를 반환하는SendAlertfunction을 만듭니다. 입력에서 notification 세부 정보를 읽고, 두 종류의 notifier를 모두 만든 다음, interface를 통해 서로 바꿔 사용할 수 있음을 보여 줍니다.
다음 입력이 제공됩니다:
- Line 1: email용 Notifier 이름
- Line 2: Email address
- Line 3: SMS용 Notifier 이름
- Line 4: Phone number
- Line 5: Alert message
BaseNotifier의 Format method는 다음을 반환해야 합니다:
[Name] messageNotify methods는 다음을 반환해야 합니다:
- EmailNotifier:
Email to [Address]: [formatted message] - SMSNotifier:
SMS to [Phone]: [formatted message]
예를 들어 Alerts, user@mail.com, Urgent, 555-1234, Server down이 주어지면 출력은 다음과 같아야 합니다:
Email to user@mail.com: [Alerts] Server down
SMS to 555-1234: [Urgent] Server down두 notifier 타입이 embedding을 통해 BaseNotifier의 Format method를 재사용하는 동시에, 각각 고유한 Notify implementation을 제공한다는 점에 주목하세요. SendAlert function은 concrete type을 알지 못해도 어떤 Notifier와도 작동합니다. 이것이 바로 inheritance 대신 composition을 사용하는 예입니다.
직접 해보기
package main
import (
"bufio"
"fmt"
"os"
)
// SendAlert는 모든 Notifier와 메시지를 받아들여 Notify 호출 결과를 반환합니다
// TODO: SendAlert 함수를 구현하세요
func main() {
scanner := bufio.NewScanner(os.Stdin)
// 이메일 notifier 이름 읽기
scanner.Scan()
emailName := scanner.Text()
// 이메일 주소 읽기
scanner.Scan()
emailAddress := scanner.Text()
// SMS notifier 이름 읽기
scanner.Scan()
smsName := scanner.Text()
// 전화번호 읽기
scanner.Scan()
phoneNumber := scanner.Text()
// 알림 메시지 읽기
scanner.Scan()
alertMessage := scanner.Text()
// TODO: emailName과 emailAddress로 EmailNotifier 생성
// TODO: smsName과 phoneNumber로 SMSNotifier 생성
// TODO: SendAlert를 사용하여 두 notifier를 통해 alertMessage 전송
// 그리고 결과 출력
// 사용하지 않는 변수 경고 억제 (변수를 사용할 때 이것들을 제거하세요)
_ = emailName
_ = emailAddress
_ = smsName
_ = phoneNumber
_ = alertMessage
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서직접 연습해 보세요: 온라인 Go 컴파일러