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

Composite Pattern, 복합체 패턴

by codeyaki 2024. 2. 5.
반응형

복합체 패턴이란?

컴포지트 패턴 혹은 복합체 패턴이라고 불리는 이 패턴은 객체들을 트리구조로 구성하여 개별객체와 복합 객체를 동일하게 취급할 수 있도록 하는 구조적 디자인패턴이다.

이를 통해서 단일객체와 복합객체를 일관된 방식으로 다룰 수 있게 된다. 

 

왜 사용해야 하는가?

  • 단일 객체와 복합 객체를 동일하게 취급하기 때문에 클라이언트 쪽에서 코드를 일관된 방식으로 사용할 수 있게 된다.
  • 재귀적인 구조로 편리하게 구현할 수 있다.
  • 새로운 단일 객체나 복합 객체를 추가할 때 기존의 코드를 수정하지 않는다.

어떤 경우에 사용해야 하는가?

  • 나무처럼 계층적인 부분-전체 구조를 갖고 있을 때 사용하면 좋다. 
    예를 들어, 그래픽 요소나, 문서 구조등이 있다.
  • 클라이언트가 일관된 인터페이스로 사용해야 할 때 사용하면 좋다.
데코레이터 패턴 모두 객체를 감싸는 데 사용한다.
하지만 데코레이터 패턴은 기능을 추가하거나 수정하는데 중점을 두지만, 
복합체는 부분과 전체를 일관된 방식으로 처리하는 데 중점을 둔다.

 

문제점

  • 재귀를 사용하기 때문에 객체가 많이 중첩된 경우 성능이 떨어질 수 있다.
  • 단일 객체와 복합객체를 동일한 인터페이스로 추상화해야 하기 때문에 설계가 어려워질 수 있다.

구현 방법

간단한 파일 시스템을 만들어 보자

여러 종류가 있지만 이중 Ordinary Files와 Directories를 가지고 와 간단하게 구현해 보자!

 

 

해당 구조를 토대로 직접 만들어보도록 한다.

먼저 File 인터페이스를 만들어서 이름과 사이즈를 구하는 메서드로 추상화를 진행하였다.

package com.example.designpattern.composite.file;

public interface File {
    int getSize();
    String getName();
}

해당 인터페이스들을 implements 일반 파일과 폴더를 구현해 주자!

 

먼저 다른 File을 담을 수 있는 복합객체인 Directory를 만들어주자.

package com.example.designpattern.composite.file;

import java.util.ArrayList;
import java.util.List;

public class Directory implements File{
    private List<File> files;
    private String name;

    public Directory(String name) {
        this.name = name;
        files = new ArrayList<>();
    }

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

    public List<File> getFiles() {
        return this.files;
    }


    @Override
    public int getSize() {
        int sumSize = 0;
        for (File file : files) {
            sumSize += file.getSize();
        }
        return sumSize;
    }

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

 

중요한 점은 getSize에 재귀적 특징을 이용해서 list에 들어있는 모든 하위 객체들의 size를 더해주는 것이다.

 

노드의 leaf에 해당하는 개별 객체인 Ordinary File 또한 구현해 주자.

package com.example.designpattern.composite.file;

public class OrdinaryFile implements File {
    private String name;
    private int size;

    public OrdinaryFile(String name, int size) {
        this.name = name;
        this.size = size;
    }

    @Override
    public int getSize() {
        return this.size;
    }

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

 

특별할 것 없는 일반적인 객체로 계층의 leaf node에 해당한다.

package com.example.designpattern.composite;

import com.example.designpattern.composite.file.Directory;
import com.example.designpattern.composite.file.File;
import com.example.designpattern.composite.file.OrdinaryFile;

public class Main {
    public static void main(String[] args) {
        // 메인 복합객체 생성하기
        Directory rootDirectory = new Directory("루트디렉터리");

        // 메인복합객체에 단일객체 넣기
        OrdinaryFile internet = new OrdinaryFile("인터넷", 50);
        OrdinaryFile notePad = new OrdinaryFile("메모장", 5);

        rootDirectory.addFile(internet);
        rootDirectory.addFile(notePad);

        // 복합객체 안에 복합객체 넣기
        Directory gameDirectory = new Directory("게임디렉터리");

        OrdinaryFile lostArk = new OrdinaryFile("잃어버린 방주", 2550);
        OrdinaryFile lol = new OrdinaryFile("전설들의 경기", 1200);

        gameDirectory.addFile(lostArk);
        gameDirectory.addFile(lol);
        rootDirectory.addFile(gameDirectory);

        // 정보 출력하기
            // 복합객체 확인
        printSize(rootDirectory);
        printSize(gameDirectory);
            // 단일 객체 확인
        printSize(lostArk);

    }

    private static void printSize(File file) {
        int sumSize = file.getSize();
        System.out.println(file.getName() + "의 용량은 " + sumSize + "입니다.");
    }
}

 

해당 클래스를 사용하는 클라이언트의 입장에선 복합객체나 개별객체들을 동일하게 사용할 수 있는 모습을 확인할 수 있다.

루트디렉터리의 용량은 3805입니다.
게임디렉터리의 용량은 3750입니다.
잃어버린 방주의 용량은 2550입니다.
반응형