컴포지트 패턴
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 107개 중 96번째.
Composite 패턴을 사용하면 개별 객체와 객체 그룹을 동일한 방식으로 다룰 수 있습니다. State가 내부 상태에 따라 동작을 변경하는 반면, Composite는 리프와 컨테이너가 동일한 인터페이스를 공유하는 트리 구조를 구성합니다.
이 패턴은 파일 시스템, 조직도 또는 UI components와 같은 계층 구조에 적합합니다. Go에서는 개별 항목과 컨테이너 모두가 implement하는 common interface를 정의합니다:
type Component interface {
GetSize() int
}
type File struct {
Name string
Size int
}
func (f File) GetSize() int {
return f.Size
}composite(컨테이너)는 자식들을 보유하고, 그 결과를 집계하여 동일한 interface를 구현합니다:
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()) // 175Composite는 부분-전체 계층 구조를 나타내야 하고 클라이언트가 개별 객체와 조합을 동일하게 처리하기를 원할 때 이상적입니다.
챌린지
쉬움Composite 패턴을 사용하여 조직도 시스템을 만들어 봅시다! 개별 직원과 부서(직원이나 다른 부서를 포함하는)를 모두 동일한 방식으로 다룰 수 있는 구조를 만들게 됩니다. 이는 조직의 어느 부분에서든 총 급여를 계산하는 데 적합합니다.
코드를 세 개의 파일로 구성합니다:
component.go: 개인과 그룹 모두가 구현할 공통 인터페이스를 정의합니다.두 개의 메서드를 가진
OrgComponent인터페이스를 만듭니다:GetSalary() int: 이 component의 총 급여를 반환합니다GetName() string: 직원 또는 부서의 이름을 반환합니다
organization.go: leaf(개별 직원)와 composite(부서) 타입을 모두 구현합니다.Name(string) 및Salary(int) 필드를 가진Employee구조체를 만듭니다.GetSalary()는 자신의 급여를 반환하고,GetName()은 자신의 이름을 반환합니다.Name(string) 필드와OrgComponent항목을 저장하는Members슬라이스를 가진Department구조체를 만듭니다. 구성원을 추가할Add(c OrgComponent)메서드를 추가합니다.GetSalary()는 모든 구성원의 급여 합계를 반환하고,GetName()은 부서 이름을 반환해야 합니다.main.go: 조직 구조를 만들고 급여를 계산합니다.직원 수를 읽습니다. 각 직원에 대해 이름과 급여를 읽고
Employee인스턴스를 생성합니다. 그런 다음 부서 수를 읽습니다. 각 부서에 대해 이름과 구성원 인덱스 개수를 읽고, 이어서 해당 인덱스들을 읽습니다(생성 순서에 따라 이전에 생성된 직원 또는 부서를 가리키는 0-based 인덱스).구조를 만든 후 최종 인덱스를 읽고, 다음 형식으로 해당 component의 이름과 총 급여를 출력합니다:
[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: 60000GetSalary()를 단일 직원에게 호출하든 전체 부서에 호출하든 동일하게 작동한다는 점에 주목하세요. Composite 패턴을 사용하면 동일한 인터페이스를 통해 개인과 그룹을 일관된 방식으로 다룰 수 있습니다!
직접 해보기
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: employee를 생성하고 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: department 생성, 구성원 인덱스 읽기, 구성원 추가, 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요약 - 파일 파서직접 연습해 보세요: 온라인 Go 컴파일러