Menu
Coddy logo textTech

컴포지트 패턴

Coddy Java 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 87개 중 75번째.

컴포지트 패턴(Composite Pattern)은 객체들을 트리 구조로 구성하여 부분-전체 계층 구조를 표현할 수 있게 해주는 구조적 디자인 패턴입니다. 이 패턴을 사용하면 클라이언트가 개별 객체와 객체들의 복합체를 공통 인터페이스를 통해 동일하게 다룰 수 있습니다.

파일 시스템을 생각해 보세요: 폴더는 파일과 다른 폴더들을 포함할 수 있으며, 그 폴더들은 다시 더 많은 파일과 폴더를 포함할 수 있습니다. 단일 파일을 다루든 전체 폴더 구조를 다루든, "크기 가져오기"와 같은 작업을 동일한 방식으로 수행하고 싶을 것입니다. 이 패턴은 Component 인터페이스, Leaf 객체(개별 요소), 그리고 Composite 객체(자식들을 포함하는 컨테이너)를 정의합니다:

interface FileComponent {
    void display(String indent);
    int getSize();
}

class File implements FileComponent {
    private String name;
    private int size;
    
    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }
    
    public void display(String indent) {
        System.out.println(indent + name + " (" + size + "KB)");
    }
    
    public int getSize() {
        return size;
    }
}

class Folder implements FileComponent {
    private String name;
    private List<FileComponent> children = new ArrayList<>();
    
    public Folder(String name) {
        this.name = name;
    }
    
    public void add(FileComponent component) {
        children.add(component);
    }
    
    public void display(String indent) {
        System.out.println(indent + name + "/");
        for (FileComponent child : children) {
            child.display(indent + "  ");
        }
    }
    
    public int getSize() {
        return children.stream().mapToInt(FileComponent::getSize).sum();
    }
}

컴포지트는 작업을 자식들에게 위임하여 재귀적인 구조를 가능하게 합니다. 클라이언트는 자신이 리프(leaf)와 작업하는지 아니면 컴포지트(composite)와 작업하는지 알지 못한 채 트리와 상호작용합니다:

Folder root = new Folder("Documents");
root.add(new File("resume.pdf", 150));

Folder photos = new Folder("Photos");
photos.add(new File("vacation.jpg", 2000));
root.add(photos);

root.display("");
System.out.println("Total: " + root.getSize() + "KB");

컴포지트 패턴(Composite Pattern)은 조직도, UI 컴포넌트 또는 메뉴 시스템과 같이 그룹과 개별 객체를 동일하게 처리해야 하는 계층 구조를 표현하는 데 이상적입니다.

challenge icon

챌린지

쉬움

컴포지트 패턴(Composite Pattern)을 사용하여 조직도 시스템을 구축해 봅시다! 부서가 직원과 다른 하위 부서를 포함할 수 있는 구조를 만들어, 단일 직원이든 전체 부서든 상관없이 총 급여를 계산하고 계층 구조를 일관되게 표시할 수 있도록 할 것입니다.

코드는 다음 네 개의 파일로 구성됩니다:

  • OrganizationComponent.java: 직원과 부서가 모두 구현할 컴포넌트 인터페이스를 정의합니다. 적절한 들여쓰기와 함께 컴포넌트를 표시하는 showDetails(String indent)와 총 급여를 정수로 반환하는 getSalary()라는 두 가지 메서드를 선언해야 합니다.
  • Employee.java: 개별 직원을 나타내는 리프(leaf) 클래스를 생성합니다. Employee는 이름(String)과 급여(int)를 가지며, 둘 다 생성자를 통해 설정됩니다. showDetails가 호출되면 [indent][name]: $[salary]를 출력해야 합니다. getSalary 메서드는 단순히 직원의 급여를 반환합니다.
  • Department.java: 직원과 하위 부서를 모두 포함할 수 있는 컴포지트(composite) 클래스를 생성합니다. Department는 이름(String)을 가지며 OrganizationComponent 자식 목록을 유지합니다. 구성원을 추가하기 위한 add(OrganizationComponent component) 메서드가 있어야 합니다. showDetails가 호출되면 [indent][name] Department를 출력한 다음, 들여쓰기를 늘려(공백 두 개 추가) 각 자식에 대해 showDetails를 호출합니다. getSalary 메서드는 모든 자식 급여의 합계를 반환합니다.
  • Main.java: 조직을 구축하세요! 네 가지 입력을 받게 됩니다: 두 명의 직원 이름과 급여, 그리고 한 명의 직원이 포함된 하위 부서 이름입니다.

    루트로 "Engineering" 부서를 생성합니다. 처음 두 개의 이름/급여 쌍을 사용하여 두 명의 직원을 추가합니다. 그런 다음 세 번째 입력을 사용하여 하위 부서를 만들고, 네 번째 이름/급여 쌍을 사용하여 해당 하위 부서에 한 명의 직원을 추가한 뒤, 이 하위 부서를 Engineering에 추가합니다.

    Engineering 부서에 대해 showDetails("")를 호출한 다음, Total Salary: $[amount] 형식으로 총 급여를 출력합니다.

입력은 다음 순서로 제공됩니다: employee1 이름 (String), employee1 급여 (int), employee2 이름 (String), employee2 급여 (int), 하위 부서 이름 (String), employee3 이름 (String), employee3 급여 (int).

예를 들어, Alice, 75000, Bob, 65000, QA, Charlie, 55000을 입력하면 출력은 다음과 같습니다:

Engineering Department
  Alice: $75000
  Bob: $65000
  QA Department
    Charlie: $55000
Total Salary: $195000

동일한 showDetailsgetSalary 메서드가 단일 직원에 대해 호출되든, 중첩된 하위 부서를 포함하는 전체 부서에 대해 호출되든 원활하게 작동하는 방식에 주목하세요. 컴포지트 구조가 재귀를 자동으로 처리하므로 전체 계층 구조를 일관되게 다룰 수 있습니다!

치트 시트

컴포지트 패턴(Composite Pattern)은 객체들을 트리 구조로 구성하여 부분-전체 계층 구조를 표현하는 구조적 디자인 패턴입니다. 이 패턴을 사용하면 개별 객체와 복합 객체를 공통 인터페이스를 통해 동일하게 다룰 수 있습니다.

이 패턴은 세 가지 핵심 요소로 구성됩니다:

  • Component: 공통 작업을 정의하는 인터페이스
  • Leaf: 컴포넌트 인터페이스를 구현하는 개별 객체
  • Composite: 자식 요소를 포함하고 작업을 자식에게 위임하는 컨테이너 객체

파일 시스템의 구현 예시:

// 컴포넌트 인터페이스
interface FileComponent {
    void display(String indent);
    int getSize();
}

// Leaf 클래스
class File implements FileComponent {
    private String name;
    private int size;
    
    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }
    
    public void display(String indent) {
        System.out.println(indent + name + " (" + size + "KB)");
    }
    
    public int getSize() {
        return size;
    }
}

// Composite 클래스
class Folder implements FileComponent {
    private String name;
    private List<FileComponent> children = new ArrayList<>();
    
    public Folder(String name) {
        this.name = name;
    }
    
    public void add(FileComponent component) {
        children.add(component);
    }
    
    public void display(String indent) {
        System.out.println(indent + name + "/");
        for (FileComponent child : children) {
            child.display(indent + "  ");
        }
    }
    
    public int getSize() {
        return children.stream().mapToInt(FileComponent::getSize).sum();
    }
}

컴포지트 구조 사용하기:

Folder root = new Folder("Documents");
root.add(new File("resume.pdf", 150));

Folder photos = new Folder("Photos");
photos.add(new File("vacation.jpg", 2000));
root.add(photos);

root.display("");
System.out.println("Total: " + root.getSize() + "KB");

컴포지트는 작업을 자식들에게 재귀적으로 위임합니다. 클라이언트는 자신이 다루는 대상이 Leaf인지 Composite인지 알지 못한 채 트리와 상호작용합니다.

사용 사례: 조직도, UI 컴포넌트, 메뉴 시스템, 파일 시스템 등 그룹과 개별 객체를 동일하게 취급해야 하는 모든 계층 구조.

직접 해보기

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 입력 읽기
        String emp1Name = scanner.nextLine();
        int emp1Salary = Integer.parseInt(scanner.nextLine());
        String emp2Name = scanner.nextLine();
        int emp2Salary = Integer.parseInt(scanner.nextLine());
        String subDeptName = scanner.nextLine();
        String emp3Name = scanner.nextLine();
        int emp3Salary = Integer.parseInt(scanner.nextLine());
        
        // TODO: "Engineering" 부서를 루트로 생성
        
        // TODO: emp1 및 emp2 데이터를 사용하여 Engineering에 두 명의 직원을 생성하고 추가
        
        // TODO: subDeptName을 사용하여 하위 부서 생성
        
        // TODO: emp3 데이터를 사용하여 하위 부서에 한 명의 직원을 생성하고 추가
        
        // TODO: Engineering에 하위 부서 추가
        
        // TODO: Engineering 부서에 대해 showDetails("") 호출
        
        // TODO: 다음 형식으로 총 급여 출력: Total Salary: $[amount]
    }
}
quiz icon실력 점검

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

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