컴포지트 패턴(Composite Pattern)

wannabeking·2022년 11월 3일
0

디자인 패턴

목록 보기
13/14
post-thumbnail

파일과 디렉토리

파일과 디렉토리는 이름을 가질 수 있다. 또한 디렉토리 안에는 파일과 디렉토리 모두 들어갈 수 있다.

하지만 트리구조인 파일 시스템에서 파일은 자식을 가질 수 없고 디렉토리는 자식들을 가질 수 있다는 차이점이 존재한다.

public class File {

    private String name;

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

    public String getName() {
        return name;
    }
}
public class Directory {

    private String name;
    private List<File> files = new ArrayList<>(); // File만 가능

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

    public String getName() {
        return name;
    }

    public void add(File file) {
        files.add(file);
    }
}

Directory는 여러 파일을 가질 수 있기 때문에 위와 같은 files를 가진다면, Directory를 추가할 수 없다. 반대로 Directory 리스트를 가진다면 File을 추가할 수 없다.

FileDirectory 모두 넣을 수 있기 위해 Object 리스트로 선언한다면 런타임 에러를 마주칠 수 있고 강제 형변환을 사용해야할 것이다.

따라서 개별 객체인 File과 복합 객체인 Directory를 추상화할 방법이 필요하고, 컴포지트 패턴이 그 해결책이다.



컴포지트 패턴

컴포지트 패턴의 정의는 다음과 같다.

컴포지트 패턴은 트리구조 구현 시 개별 객체와 복합 객체를 똑같은 방법으로 다룰 수 있다.

컴포지트 패턴은 Leaf(개별 객체)와 Composite(복합 객체)를 Component로 추상화한다.

코드를 살펴보자.


public abstract class Component {

    public void add(Component component) {
        throw new UnsupportedOperationException();
    }

    public void remove(Component component) {
        throw new UnsupportedOperationException();
    }

    public Component getChild(int i) {
        throw new UnsupportedOperationException();
    }

    public String getName() {
        throw new UnsupportedOperationException();
    }

    public void setName(String name) {
        throw new UnsupportedOperationException();
    }
}

우선 Component이다.

복합 객체는 자식들을 가지고 개별 객체는 그렇지 않으므로 두 클래스 간 메소드의 종류에서 차이가 발생할 수 있다.

중복되는 메소드만 인터페이스로 구현하고 복합 객체에서 추가적으로 메소드를 새롭게 구현하는 방법도 있지만, 추상 클래스를 사용하면 선택적으로 오버라이드 가능하다.

개별 객체에서는 add()를 구현하지 않을 것이므로, 호출 시 UnsupportedOperationException가 발생할 것이다.


public class File extends Component {

    private String name;

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

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }
}

개별 객체인 Filename의 Setter & Getter만 구현했다.

public class Directory extends Component {

    private String name;
    private List<Component> components = new ArrayList<>();

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

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void add(Component component) {
        components.add(component);
    }

    @Override
    public void remove(Component component) {
        components.remove(component);
    }

    @Override
    public Component getChild(int i) {
        return components.get(i);
    }
}

복합 객체인 Directory는 자식들을 가질수 있기 때문에 필드에 components를 가지고 있다.

자식들을 추가, 삭제, 가져오기 위한 add(), remove(), getChild()를 구현했고 File과 마찬가지로 Directory도 이름을 가질 수 있기 때문에 name의 Getter & Setter가 존재한다.

출력 결과를 확인해보자.


public static void main(String[] args) {
    Directory directory1 = new Directory("directory1");
    Directory directory2 = new Directory("directory2");
    File file1 = new File("file1");
    File file2 = new File("file2");
    directory1.add(file1);
    directory2.add(file2);
    directory1.add(directory2);
    
    System.out.println(directory1.getName());
    System.out.println(directory1.getChild(0).getName());
    System.out.println(directory1.getChild(1).getName());
    System.out.println(directory1.getChild(1).getChild(0).getName());
}

개별 객체와 복합 객체를 모두 Component로 추상화했기 때문에 DirectoryFileDirectory를 모두 자식으로 가질 수 있다.

출력 결과는 다음과 같다.


파일과 디렉토리를 예시로 컴포지트 패턴에 대해 알아 보았다.

부분-전체 계층구조를 가진 객체 컬렉션에서 그 객체들을 모두 똑같은 방식으로 다루고 싶을 때, 컴포지트 패턴을 떠올리자.


모든 소스코드는 여기에서 확인할 수 있다.



profile
내일은 개발왕 😎

0개의 댓글