Menu
Coddy logo textTech

컴포지트 패턴

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())  // 175

Composite는 부분-전체 계층 구조를 나타내야 하고 클라이언트가 개별 객체와 조합을 동일하게 처리하기를 원할 때 이상적입니다.

challenge icon

챌린지

쉬움

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: 60000

GetSalary()를 단일 직원에게 호출하든 전체 부서에 호출하든 동일하게 작동한다는 점에 주목하세요. 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)
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: 온라인 Go 컴파일러