Go 언어에서 조합(composition) 지원은 embedding 메커니즘을 통해 구현됩니다. 이는 한 구조체가 다른 구조체 내에 명시적으로 필드 이름 없이 포함될 수 있는 기능을 의미합니다. 이러한 접근 방식은 상속을 "자신만의 방식"으로 모델링하면서 언어의 간결성을 유지하고 다중 상속과 관련된 많은 복잡성을 피할 수 있도록 하려는 목적이 있었습니다.
개발자는 종종 기본 구조체의 동작이나 인터페이스를 확장하고 싶어하지만 아키텍처를 복잡하게 만들고 싶지 않습니다. Go에서는 이러한 용도로 embedding을 자주 사용하며, 이는 중첩된 타입의 메서드와 필드에 직접 접근할 수 있게 해줍니다. 마치 그것들이 부모 구조체에 정의된 것처럼 사용할 수 있게 됩니다. 그러나 embedding에는 고유한 특성이 있으며, 이 메커니즘에 대한 잘못된 이해는 이름 충돌, 이중 포함(double embedding), 메서드의 잘못된 상속과 같은 예기치 않은 오류를 초래할 수 있습니다.
embedding을 올바르게 경제적으로 사용하면 더 깔끔한 조합을 얻을 수 있습니다. 메서드와 필드는 "하나의 레벨"만 상승하며, embedding은 "has-a" 관계를 구현한다는 점을 기억해야 합니다. 이름 충돌을 피하고 메서드 수신자(receiver)가 어떻게 작동하는지 이해하는 것이 중요합니다.
코드 예제:
package main import "fmt" type Engine struct { Power int } func (e Engine) Start() { fmt.Println("엔진이 힘", e.Power, "으로 시작되었습니다") } type Car struct { Engine // embedding, 필드 Engine이 아닌 Brand string } func main() { c := Car{Engine: Engine{Power: 200}, Brand: "Toyota"} c.Start() // 직접 접근 가능 fmt.Println(c.Power) // 필드도 상승됨 }
주요 특징:
embedding이 다중 상속을 구현할 수 있습니까?
아니요, embedding은 OOP에서의 상속을 구현하지 않으며 조합입니다. 하나의 구조체에 여러 개의 다른 구조체를 포함할 수 있으나, 메서드가 충돌할 경우 병합이 아니라 컴파일 오류가 발생합니다.
중첩 구조체의 필드 이름이 동일하면 어떻게 됩니까?
직접 접근 시 컴파일 오류가 발생합니다: 컴파일러는 어떤 필드에 접근해야 할지 모릅니다. 중첩 구조체의 이름을 통해 경로를 명시적으로 지정해야 합니다.
코드 예제:
type A struct {X int} type B struct {X int} type C struct { A B } func main() { c := C{} // c.X = 1 // 오류: 모호한 선택자 c.A.X = 1 // 이렇게만 }
embedding 시 값/포인터 메서드가 동일하게 상승합니까?
아니요. 포인터 수신자(receiver)와 함께 있는 메서드는 구조체가 포인터를 사용할 경우에만 상승합니다(c := &Car{}). 값으로 구조체를 사용할 경우 포인터 수신자와 함께하는 메서드는 "상승하지 않습니다".
깊게 중첩된 구조체로 프로젝트가 구성되어 있으며, embedding은 다단계 상속을 모방하는 데 사용됩니다. 초보자는 필드나 메서드가 어떤 구조체에서 오는지 이해하지 못하고, 전체 프로젝트가 복잡해지고 "마법 같아"집니다.
장점:
단점:
인터페이스 구현을 위해 embedding이 사용되는 경우, 예를 들어 Logger 인터페이스가 Println 메서드를 가진 타입을 통해 embedding으로 구현되며, 이는 명확하게 문서화되고 테스트로 검증됩니다.
장점:
단점: