본문 바로가기
Design Pattern/구조 패턴(Structural Patterns)

컴포지트(Composite) 패턴 - 구조 패턴 (Structural Patterns)

by 김 민 준 2024. 5. 23.

 

컴포지트 패턴은 개체들을 트리 구조로 구성하여 부분-전체 계층을 나타내는 디자인 패턴이다. 

 

이 패턴을 사용하면 클라리언트가 단일 개체와 복합 개체(여러 개체로 구성된 개체)를 동일하게 다를 수 있다. 개별 개체와 여러 개체로 구성된 그룹을 같은 방식으로 처리할 수 있게 해준다. 

 

간단하게 예시로 파일 시스템을 생각해보자

 

파일 시스템의 구조는 파일과 폴더일것이다. 이때 파일은 개별 개체이고 폴더는 여러 파일이나 다른 폴더를 포함할 수 있는 복합 개체이다. 컴포징트 패턴을 사용하면 파일과 폴더를 같은 방식으로 처리할 수 있다. 

 

1. Component(구성 요소) : 파일과 폴더의 공통 인터페이스 

2. Leaf(잎) : 개별 개체, 본 예제에서는 파일을 의미

3. Composite (복합 개체) : 복합 개체, 본 예제에서는 폴더 

 

예시 소스코드를 살펴보자

 

Component 인터페이스 

 

// Component 인터페이스: 파일과 폴더의 공통 기능 정의
interface FileSystemComponent {
    void showDetails();
}

 

Leaf 클래스 

 

// File 클래스: 파일, 잎 역할
class File implements FileSystemComponent {
    private String name;

    public File(String name) {
        this.name = name;
    }

    @Override
    public void showDetails() {
        System.out.println("File: " + name);
    }
}

 

Composite 클래스 

 

// Folder 클래스: 폴더, 복합 객체 역할
import java.util.ArrayList;
import java.util.List;

class Folder implements FileSystemComponent {
    private String name;
    private List<FileSystemComponent> components = new ArrayList<>();

    public Folder(String name) {
        this.name = name;
    }

    public void addComponent(FileSystemComponent component) {
        components.add(component);
    }

    public void removeComponent(FileSystemComponent component) {
        components.remove(component);
    }

    @Override
    public void showDetails() {
        System.out.println("Folder: " + name);
        for (FileSystemComponent component : components) {
            component.showDetails();
        }
    }
}

 

이렇게 구현한 소스코드를 어떻게 사용하면 되는지 아래 소스코드를 살펴보자.

 

// 사용 예시
public class CompositePatternDemo {
    public static void main(String[] args) {
        // 개별 파일 생성
        File file1 = new File("File1.txt");
        File file2 = new File("File2.jpg");
        File file3 = new File("File3.doc");

        // 폴더 생성
        Folder folder1 = new Folder("Folder1");
        Folder folder2 = new Folder("Folder2");

        // 파일을 폴더에 추가
        folder1.addComponent(file1);
        folder1.addComponent(file2);

        folder2.addComponent(file3);
        folder2.addComponent(folder1); // 폴더 안에 폴더 추가

        // 전체 구조 출력
        folder2.showDetails();
        
        
    // 출력 내용
    //Folder: Folder2
    //File: File3.doc
    //Folder: Folder1
    //File: File1.txt
    //File: File2.jpg



    }
}

 

파일과 폴더를 만드는 구조를 컴포지트 패턴으로 구현했다. 

개별적으로 파일을 생성하는것 그리고 폴더를 생성하는것을 포함하여 파일을 폴더에 추가하는것까지 구현되었다.

 

이처럼 개별 개체와 복합 개체를 다뤄야하는 경우에 컴포지트 패턴을 이용하면 동일한 방식으로 구현함에 있어 사용자로 하여금 통일감을 느낄 수 있게 된다. 

그럼 실무에서 가장 많이 사용하는 예시를 생각해보자.

 

요즘은 함수형으로 많이 사용하지만 나는 최근까지 React를 컴포넌트 클래스로 사용한 경험이 있다.

이 경험을 생각해보면 컴포지트 패턴을 사용했음을 알 수 있다. 

예시 소스 코드를 살펴보자.

 

// Button.js
import React from 'react';

const Button = ({ label }) => {
    return (
        <button>{label}</button>
    );
}

export default Button;

 

 

// App.js
import React from 'react';
import Button from './Button';

const App = () => {
    return (
        <div>
            <h1>My App</h1>
            <Button label="Click Me" />
            <Button label="Submit" />
        </div>
    );
}

export default App;

 

이와 같이 React에서 컴포지트 패턴을 활용하여 구현하면 재사용하기 좋아 다양한 조합으로 UI를 쉽게 구성할 수 있다.

또한 유지보수에도 좋은데 각 컴포턴트를 독립적으로 수정할 수 있기 때문이다.

가독성에서도 좋은데 트리형태로 명확하게 표현되기 때문이다.