S E P H ' S

[구조 패턴] 컴포지트(Composite) 패턴 본문

Programing & Coding/Design Pattern

[구조 패턴] 컴포지트(Composite) 패턴

yoseph0310 2023. 3. 19. 13:47

컴포지트(Composite) 패턴

OOP에서 컴포지트는 하나 이상의 유사한 객체를 구성으로 설계된 객체로 모두 유사한 기능을 나타낸다.

이를 통해 객체 그룹을 조작하는 것처럼, 단일 객체를 조작할 수 있다.

 

컴포지트 패턴이란?

컴포지트 패턴은 클라이언트가 복합 객체(group of object)나 단일 객체를 동일하게 취급하는 것을 목적으로 한다.

여기서 컴포지트의 의도는 트리 구조로 작성하여, 전체-부분(whole-part)관계를 표현하는 것이다.

트리구조를 다룰때, 프로그래머는 리프노드와 브랜치를 구별해야 한다.

여기서 코드는 많은 복잡성을 만들어 많은 에러를 초래한다.

이를 해결하기 위해, 복잡하고 원시적인 객체를 동일하게 취급하기 위한 인터페이스를 작성할 수 있다.

이러한 컴포지트 패턴은 인터페이스와 본연의 컴포지트 개념을 활용한다.

 

언제 사용할까?

복합 객체와 단일 객체의 처리 방법이 다르지 않을 경우, 전체-부분 관계로 정의할 수 있다.

전체-부분 관계의 대표적 예시는 directory - file 이 존재한다.

이러한 전체-부분 관계를 효율적으로 정의할 때 유용하다.

  • 전체-부분 관계를 트리 구조로 표현하고 싶을 경우
  • 전체-부분 관계를 클라이언트에서 부분, 관계를 균일하게 처리하고 싶을 경우

 

구조
  • Component : 컴포지션(구성 요소들)의 모든 개체에 대한 기본 인터페이스이다. Composite, Leaf을 관리하는 공통 메소드가 있는 인터페이스 또는 추상 클래스여야 한다.
  • Leaf : 기본 구성 요소의 기본 동작을 구현한다. 다른 개체에 대한 참조를 포함하지 않는다.
  • Composite : 리프 요소가 있다. 기본 구성 요소 메소드를 구현하고 자식 관련 작업을 정의한다.
  • Client - 기본 구성 요소 개체를 사용하여 컴포지션 요소에 액세스 할 수 있다.

컴포지트 패턴 구성 요소

 

장단점

 

장점

  • 객체들이 모두 같은 타입으로 취급되므로 새로운 클래스 추가가 용이하다.
  • 단일 객체 및 집합 객체를 구분하지 않고 코드 작성이 가능하여 사용자 코드가 단순해진다.
  • 단일 객체와 집합 객체로 구성된 하나의 일관된 클래스 계통을 정의한다. 런타임 단일 객체와 집합 객체를 구분 하지 않고 일관된 프로그래밍이 가능하다.

단점

  • 설계가 지나치게 범용성을 많이 가진다. 기능이 크게 다른 클래스에 공통 인터페이스를 제공하는 것이 어렵다.
  • 즉, 설계를 일반화 시켜 객체간의 구분, 제약이 힘들다.

 

예시

기본 구성 요소 (The Base Component)

먼저 구성 요초 개체로 부서 인터페이스를 정의했다.

public interface Department {
    void printDepartmentName();
}

 

리프 (Leafs)

리프 요소에는 재무 및 영업 부서에 대한 클래스를 정의했다.

public class FinancialDepartment implements Department {

    private Integer id;
    private String name;

    public void printDepartmentName() {
        System.out.println(getClass().getSimpleName());
    }

    // standard constructor, getters, setters
}
public class SalesDepartment implements Department {

    private Integer id;
    private String name;

    public void printDepartmentName() {
        System.out.println(getClass().getSimpleName());
    }

    // standard constructor, getters, setters
}

 

주의 깊게 봐야 할 것은 두 클래스 모두 기본 구성 요소에서 printDepartmentName() 메소드를 구현하여 각각의 클래스 이름을 출력한다.

또한 이들은 리프 클래스이므로 다른 부서 개체를 포함하지 않는다.

 

컴포지트 (The Composite Element)

public class HeadDepartment implements Department {
    private Integer id;
    private String name;

    private List<Department> childDepartments;

    public HeadDepartment(Integer id, String name) {
        this.id = id;
        this.name = name;
        this.childDepartments = new ArrayList<>();
    }

    public void printDepartmentName() {
        childDepartments.forEach(Department::printDepartmentName);
    }

    public void addDepartment(Department department) {
        childDepartments.add(department);
    }

    public void removeDepartment(Department department) {
        childDepartments.remove(department);
    }
}

HeadDepartment는 Department 구성 요소 컬렉션과 목록에서 요소를 추가하고 제거하는 메소드를 보유하는 복합 클래스이다.

HeadDepartment의 printDepartmentName에서는 모든 리프 요소 목록을 출력하도록하고 각각에 대한 적절한 메소드를 호출하여 구현된다.

 

테스트

 

public class CompositeDemo {
    public static void main(String[] args) {
        Department salesDepartment = new SalesDepartment(1, "Sales Department");
        Department financialDepartment = new FinancialDepartment(2, "Financial Department");

        HeadDepartment headDepartment = new HeadDepartment(3, "Head Department");

        headDepartment.addDepartment(salesDepartment);
        headDepartment.addDepartment(financialDepartment);

        headDepartment.printDepartmentName();
    }
}

 

 

재무 및 영업 부서에 대한 인스턴스를 생성하고 헤드 부서를 인스턴스화 한다. 그리고 헤드 부서에 영업 부서와 재무 부서를 추가시키고 출력을 하면 두 부서의 이름이 출력된다. 이렇게 구조 패턴 중 컴포지트 패턴에 대해 알아봤다. 설계 도중 각 요소들이 공통된 동작을 하고 이에 대해 동일하게 관리가 필요하다면 컴포지트 패턴을 떠올려 코드를 작성하면 되겠다는 생각을 하게 되었다.