Menu
CoddyTech

컴포지트 패턴

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

컴포지트 패턴은 객체를 트리 구조로 구성해 전체와 부분의 계층 구조를 표현할 수 있게 하는 구조 설계 패턴입니다. 공통 인터페이스를 통해 클라이언트가 개별 객체와 객체의 조합을 동일한 방식으로 다룰 수 있게 합니다.

파일 시스템을 떠올려 보세요. 폴더에는 파일과 다른 폴더가 들어갈 수 있고, 그 폴더에도 더 많은 파일과 폴더가 들어갈 수 있습니다. 단일 파일을 다루든 전체 폴더 구조를 다루든, "크기 가져오기"와 같은 작업을 같은 방식으로 수행하고 싶을 것입니다. 이 패턴은 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();
    }
}

컴포지트는 작업을 자식에게 위임하여 재귀적인 구조를 가능하게 합니다. 클라이언트는 리프와 컴포지트 중 어느 쪽을 다루는지 알지 못한 채 트리와 상호작용합니다.

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");

컴포지트 패턴은 조직도, UI 구성 요소 또는 메뉴 시스템처럼 그룹과 개별 항목을 동일하게 다뤄야 하는 계층 구조를 표현하는 데 적합합니다.

challenge icon

챌린지

쉬움

Composite 패턴을 사용해 조직도 시스템을 만들어 봅시다! 부서가 직원과 다른 하위 부서를 포함할 수 있는 구조를 만들고, 단일 직원이나 부서 전체를 살펴볼 때 모두 동일한 방식으로 총급여를 계산하고 계층 구조를 표시해 보세요.

코드를 네 개의 파일로 나눠 작성합니다.

  • OrganizationComponent.java: 직원과 부서가 모두 구현할 컴포넌트 인터페이스를 정의합니다. 컴포넌트를 적절한 들여쓰기로 표시하는 showDetails(String indent) 메서드와 총급여를 정수로 반환하는 getSalary() 메서드를 선언해야 합니다.
  • Employee.java: 개별 직원을 나타내는 리프 클래스를 만듭니다. Employee에는 이름(String)과 급여(int)가 있으며, 둘 다 생성자를 통해 설정합니다. showDetails가 호출되면 [indent][name]: $[salary]를 출력해야 합니다. getSalary 메서드는 직원의 급여를 반환합니다.
  • Department.java: 직원과 하위 부서를 모두 포함할 수 있는 복합 클래스를 만듭니다. 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

showDetails와 getSalary 메서드는 단일 직원이나 중첩된 하위 부서를 포함하는 부서 전체에서 호출해도 동일하게 원활히 작동합니다. 복합 구조가 재귀를 자동으로 처리하므로 전체 계층 구조를 일관된 방식으로 다룰 수 있습니다!

직접 해보기

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 데이터를 사용해 직원 2명을 생성하고 Engineering에 추가하기
        
        // TODO: subDeptName을 사용해 하위 부서 생성하기
        
        // TODO: emp3 데이터를 사용해 직원 1명을 생성하고 하위 부서에 추가하기
        
        // TODO: 하위 부서를 Engineering에 추가하기
        
        // TODO: Engineering 부서에서 showDetails("") 호출하기
        
        // TODO: 총 급여를 Total Salary: $[amount] 형식으로 출력하기
    }
}
quiz icon실력 점검

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

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

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