컴포지트 패턴
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 96번째.
컴포지트 패턴(Composite pattern)은 개별 객체와 객체 그룹을 동일하게 다룰 수 있도록 해줍니다. 상태(State) 패턴이 내부 상태에 따라 동작을 변경하는 반면, 컴포지트(Composite)는 리프(leaf)와 컨테이너(container)가 동일한 인터페이스를 공유하는 트리 구조를 구축합니다.
이 패턴은 파일 시스템, 조직도 또는 UI 컴포넌트와 같은 계층적 구조에 적합합니다. Go에서는 개별 항목과 컨테이너가 모두 구현하는 공통 인터페이스를 정의합니다:
type Component interface {
GetSize() int
}
type File struct {
Name string
Size int
}
func (f File) GetSize() int {
return f.Size
}컴포지트(컨테이너)는 자식들을 포함하며 그들의 결과를 집계함으로써 동일한 인터페이스를 구현합니다:
type Folder struct {
Name string
Children []Component
}
func (f *Folder) Add(c Component) {
f.Children = append(f.Children, c)
}
func (f Folder) GetSize() int {
total := 0
for _, child := range f.Children {
total += child.GetSize()
}
return total
}이제 폴더 안에 폴더를 중첩할 수 있으며, GetSize()를 호출하는 것은 단일 파일이든 전체 디렉토리 트리이든 상관없이 동일하게 작동합니다:
docs := &Folder{Name: "docs"}
docs.Add(File{Name: "readme.txt", Size: 100})
docs.Add(File{Name: "notes.txt", Size: 50})
root := &Folder{Name: "root"}
root.Add(docs)
root.Add(File{Name: "config.json", Size: 25})
fmt.Println(root.GetSize()) // 175컴포지트 패턴은 부분-전체 계층 구조를 표현해야 하고, 클라이언트가 개별 객체와 복합 객체를 동일하게 다루기를 원할 때 이상적입니다.
챌린지
쉬움Composite 패턴을 사용하여 조직도 시스템을 구축해 봅시다! 개별 직원과 부서(직원 또는 다른 부서를 포함하는)를 동일하게 취급할 수 있는 구조를 만들게 됩니다. 이는 조직의 어느 부분에서든 총 급여를 계산하는 데 매우 적합합니다.
코드는 세 개의 파일로 구성됩니다:
component.go: 개인과 그룹이 모두 구현할 공통 인터페이스를 정의합니다.두 개의 메서드를 가진
OrgComponent인터페이스를 생성하세요:GetSalary() int— 이 컴포넌트의 총 급여를 반환합니다.GetName() string— 직원 또는 부서의 이름을 반환합니다.
organization.go: 리프(개별 직원) 및 컴포지트(부서) 타입을 모두 구현합니다.Name(string)과Salary(int) 필드를 가진Employee구조체를 생성하세요. 이 구조체의GetSalary()는 자신의 급여를 반환하고,GetName()은 자신의 이름을 반환합니다.Name(string) 필드와OrgComponent항목을 담는Members슬라이스를 가진Department구조체를 생성하세요. 멤버를 추가하기 위한Add(c OrgComponent)메서드를 추가하세요. 이 구조체의GetSalary()는 모든 멤버의 급여 합계를 반환해야 하며,GetName()은 부서 이름을 반환합니다.main.go: 조직 구조를 구축하고 급여를 계산합니다.직원 수를 읽습니다. 각 직원에 대해 이름과 급여를 읽고
Employee인스턴스를 생성합니다. 그런 다음 부서 수를 읽습니다. 각 부서에 대해 이름과 멤버 인덱스 개수를 읽은 다음, 해당 인덱스들(생성된 순서대로 이전의 직원 또는 부서를 참조하는 0부터 시작하는 인덱스)을 읽습니다.구조를 구축한 후, 마지막 인덱스를 읽고 해당 컴포넌트의 이름과 총 급여를
[name]: [salary]형식으로 출력합니다.
다음 입력이 제공됩니다:
- 직원 수, 그 다음 각 직원의 이름과 급여가 별도의 줄에 제공됩니다.
- 부서 수, 그 다음 각 부서의 이름, 멤버 수, 멤버 인덱스가 제공됩니다.
- 조회할 마지막 인덱스
예를 들어, 다음과 같이 입력되면:
3
Alice
50000
Bob
60000
Carol
55000
1
Engineering
3
0
1
2
3출력은 다음과 같아야 합니다:
Engineering: 165000그리고 다음과 같이 입력되면:
4
Alice
50000
Bob
60000
Carol
55000
Dave
70000
2
Dev
2
0
1
QA
2
2
3
4출력은 다음과 같아야 합니다:
QA: 125000그리고 다음과 같이 입력되면:
2
Alice
50000
Bob
60000
0
1출력은 다음과 같아야 합니다:
Bob: 60000단일 직원에 대해 호출하든 전체 부서에 대해 호출하든 GetSalary()가 동일하게 작동하는 방식에 주목하세요. Composite 패턴을 사용하면 동일한 인터페이스를 통해 개인과 그룹을 균일하게 처리할 수 있습니다!
치트 시트
컴포지트 패턴(Composite pattern)은 개별 객체와 객체 그룹을 공통 인터페이스를 통해 동일하게 다룹니다. 파일 시스템, 조직도 또는 UI 컴포넌트와 같은 계층 구조에 이상적입니다.
개별 항목과 컨테이너가 모두 구현하는 공통 인터페이스를 정의합니다:
type Component interface {
GetSize() int
}리프(leaf, 개별 항목)를 구현합니다:
type File struct {
Name string
Size int
}
func (f File) GetSize() int {
return f.Size
}자식 요소를 포함하고 그 결과를 집계하는 컴포지트(composite, 컨테이너)를 구현합니다:
type Folder struct {
Name string
Children []Component
}
func (f *Folder) Add(c Component) {
f.Children = append(f.Children, c)
}
func (f Folder) GetSize() int {
total := 0
for _, child := range f.Children {
total += child.GetSize()
}
return total
}개별 항목과 복합체를 동일하게 처리하는 사용 예시입니다:
docs := &Folder{Name: "docs"}
docs.Add(File{Name: "readme.txt", Size: 100})
docs.Add(File{Name: "notes.txt", Size: 50})
root := &Folder{Name: "root"}
root.Add(docs)
root.Add(File{Name: "config.json", Size: 25})
fmt.Println(root.GetSize()) // 175직접 해보기
package main
import "fmt"
func main() {
// 직원 수 읽기
var numEmployees int
fmt.Scanln(&numEmployees)
// 모든 구성 요소(직원 및 부서)를 생성 순서대로 저장
var components []OrgComponent
// TODO: 각 직원의 이름과 급여 읽기
// Employee 인스턴스를 생성하고 components 슬라이스에 추가
for i := 0; i < numEmployees; i++ {
var name string
var salary int
fmt.Scanln(&name)
fmt.Scanln(&salary)
// TODO: 직원을 생성하고 components에 추가
}
// 부서 수 읽기
var numDepartments int
fmt.Scanln(&numDepartments)
// TODO: 각 부서의 이름, 구성원 수 및 구성원 인덱스 읽기
// Department 인스턴스를 생성하고, 인덱스로 구성원을 추가한 뒤 components에 추가
for i := 0; i < numDepartments; i++ {
var deptName string
var memberCount int
fmt.Scanln(&deptName)
fmt.Scanln(&memberCount)
// TODO: 부서를 생성하고, 구성원 인덱스를 읽어 구성원을 추가한 뒤 components에 추가
}
// 조회할 최종 인덱스 읽기
var queryIndex int
fmt.Scanln(&queryIndex)
// TODO: 구성 요소의 이름과 총 급여를 다음 형식으로 출력: [name]: [salary]
// 예시: fmt.Printf("%s: %d\n", name, salary)
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서