임베딩 vs 집약
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 38번째.
여러분은 임베딩(embedding)이 필드와 메서드를 외부 구조체로 승격시킨다는 것을 배웠습니다. 하지만 Go는 다른 구조체를 이름이 있는 필드로 포함하는 집합(aggregation) 방식도 지원합니다. 각 방식을 언제 사용해야 하는지 이해하는 것이 좋은 설계의 핵심입니다.
임베딩을 사용하면 필드 이름을 생략할 수 있으며, 내부 타입의 멤버들에 직접 접근할 수 있게 됩니다:
type Writer struct{}
func (w Writer) Write() string { return "writing" }
type Document struct {
Writer // 임베딩 - 필드 이름 없음
}
d := Document{}
d.Write() // 직접 접근
애그리게이션(aggregation)을 사용하면 필드에 명시적인 이름을 부여하게 되며, 내부 타입에 접근하려면 해당 이름이 필요합니다:
type Document struct {
writer Writer // 애그리게이션 - 명명된 필드
}
d := Document{writer: Writer{}}
d.writer.Write() // 필드 이름을 사용해야 함
핵심적인 차이점은 표현하고자 하는 관계입니다. 임베딩(Embedding)은 외부 타입이 내부 타입의 정체성을 갖게 되는 "is-a" 관계를 나타냅니다. 집합(Aggregation)은 외부 타입이 단순히 내부 타입을 포함하는 "has-a" 관계를 나타냅니다.
외부 타입이 내부 타입에 의해 구현된 인터페이스를 만족하도록 하거나, 직접적인 메서드 접근을 통해 API를 더 깔끔하게 만들고 싶을 때 임베딩(embedding)을 사용하세요. 내부 타입이 노출되어서는 안 되는 구현 세부 사항이거나, 내부 타입에 접근하는 방식을 명시적으로 제어하고 싶을 때는 어그리게이션(aggregation)을 사용하세요.
챌린지
쉬움임베딩(embedding)과 집합(aggregation)을 언제 사용해야 하는지 보여주는 보고서 생성 시스템을 구축해 보겠습니다. 일부 구성 요소는 직접 액세스할 수 있어야 하고(임베딩), 다른 구성 요소는 내부 구현 세부 사항으로 유지되어야 하는(집합) 시스템을 만들 것입니다.
코드는 세 개의 파일로 구성됩니다:
formatter.go:Style필드(string)와Format(text string) string메서드를 가진Formatter구조체를 생성합니다. 이 메서드는[Style] text [/Style]와 같이 텍스트를 스타일로 감싸서 반환해야 합니다. 이것은 직접 노출되지 않아야 하는 내부 헬퍼가 될 것입니다.report.go: 보고서 생성을 다르게 처리하는 두 개의 구조체를 생성합니다:Author및Date필드를 가진Metadata구조체 — 이것은 직접 액세스할 수 있어야 하는 공개 정보를 나타냅니다.Title필드를 가진Report구조체 — 이 구조체는Metadata를 임베드(작성자와 날짜에 직접 액세스하기 위해)하지만,Formatter는 집합(formatter라는 이름의 필드를 가진 내부 구현 세부 사항으로)을 사용합니다.
Generate() string메서드를Report에 추가합니다.main.go: 입력에서 보고서 세부 정보를 읽고, 임베드된 메타데이터와 집합된 포매터를 모두 가진Report를 생성합니다. 보고서에서 작성자에게 직접 액세스하고(임베딩을 통해), 형식이 지정된 보고서를 생성(내부적으로 숨겨진 포매터를 사용)함으로써 차이점을 보여줍니다.
다음 입력이 제공됩니다:
- 1행: 보고서 제목
- 2행: 작성자 이름
- 3행: 날짜
- 4행: 포매터 스타일
Format 메서드는 다음을 반환해야 합니다:
[Style] text [/Style]Generate 메서드는 다음을 반환해야 합니다:
[formatted title]
Author: [Author], Date: [Date]메인 파일에서 다음 세 줄을 출력합니다:
- Report에서 직접 액세스한 작성자 (임베딩이 필드를 승격시킴을 보여줌)
- 임베드된 타입 이름을 통해 액세스한 날짜 (
report.Metadata.Date) Generate()호출 결과
예를 들어, Quarterly Results, Jane Doe, 2024-01-15, BOLD가 주어지면 출력은 다음과 같아야 합니다:
Jane Doe
2024-01-15
[BOLD] Quarterly Results [/BOLD]
Author: Jane Doe, Date: 2024-01-15임베딩을 통해 Author와 Date는 Report에서 직접 액세스할 수 있는 반면, 포매터는 숨겨져 있는 방식에 주목하세요. 포매터는 이름이 있는 필드로 집합되어 있기 때문에 report.Format()을 직접 호출할 수 없습니다. 이것은 실제 사례에서 "is-a" 관계와 "has-a" 관계의 차이를 보여줍니다.
치트 시트
Go는 한 구조체를 다른 구조체 내에 포함하는 두 가지 방법을 지원합니다: 임베딩(embedding)과 애그리게이션(aggregation).
임베딩(Embedding)은 필드 이름을 생략하여, 내부 타입의 필드와 메서드를 외부 구조체로 승격(promote)시킵니다:
type Writer struct{}
func (w Writer) Write() string { return "writing" }
type Document struct {
Writer // embedding - no field name
}
d := Document{}
d.Write() // direct access to embedded method
애그리게이션(Aggregation)은 명시적인 필드 이름을 사용하며, 내부 타입에 접근하기 위해 해당 이름이 필요합니다:
type Document struct {
writer Writer // aggregation - named field
}
d := Document{writer: Writer{}}
d.writer.Write() // must use field name
각각을 사용하는 경우:
- 임베딩(Embedding): "is-a" 관계를 표현합니다. 외부 타입이 내부 타입의 인터페이스를 충족해야 하거나, 직접적인 메서드 접근이 API를 개선할 때 사용합니다.
- 애그리게이션(Aggregation): "has-a" 관계를 표현합니다. 내부 타입이 노출되어서는 안 되는 구현 세부 사항이거나, 접근에 대한 명시적인 제어를 원할 때 사용합니다.
임베딩을 사용하더라도 필요한 경우 임베딩된 타입 이름을 통해 필드에 접근할 수 있습니다:
type Metadata struct {
Author string
}
type Report struct {
Metadata // embedded
}
r := Report{Metadata: Metadata{Author: "Jane"}}
r.Author // direct access via promotion
r.Metadata.Author // explicit access via type name
직접 해보기
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
title := scanner.Text()
scanner.Scan()
author := scanner.Text()
scanner.Scan()
date := scanner.Text()
scanner.Scan()
style := scanner.Text()
// TODO: 작성자와 날짜를 포함하는 Metadata 구조체 생성
// TODO: 주어진 스타일을 사용하는 Formatter 생성
// TODO: Metadata를 임베드하고 Formatter를 집합(aggregate)하는 Report 생성
// TODO: Report에서 직접 접근한 작성자 출력 (임베딩은 필드를 승격시킴)
// TODO: 임베드된 타입 이름을 통해 접근한 날짜 출력 (report.Metadata.Date)
// TODO: Generate() 호출 결과 출력
// 사용하지 않는 변수 경고 억제 (변수를 사용할 때 이 줄들을 제거하세요)
_ = title
_ = author
_ = date
_ = style
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서