ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Design Pattern] Composite Pattern
    Learn/Architecture 2022. 9. 25. 01:16

    # 개요

    계층(Hierarchy)이 있는 객체들을 동일하게 다루기 위해 사용하는 패턴

    위와 같이 계층이 있을 경우 아래와 같이 계층에 따라 다르게 핸들링하지 않기 위한 패턴이다. 

    if ( current instanceOf(MenuItem)) 
    // … handle Menu Item in a way
    else if (o instanceOf(Menu)) 
    // … handle Menu in another way

    # Composite Pattern

    아래와 같이 Decorator Pattern과 유사하게 디자인 할 수 있다. 

    • 클라이언트는 MenuComponent를 통해 MenuItem과 Menu에 동일하게 접근할 수 있다.
    • Decorator Pattern처럼 Association, Inheritance를 모두 가지고 있다. 

    일반화하면 다음과 같다. 

    • 자식이 없는건 Leaf로 표현한다. 

    # 예시 코드

    Component Class

    • Component는 인터페이스의 역할만 할 뿐 그 자체의 인스턴스는 의미가 없으므로 예시에서는 다 exception 처리
    public class MenuComponent {
        public void add(MenuComponent menuComponent) {
            throw new UnsupportedOperationException(); 
        }
        public void remove(MenuComponent menuComponent) {
        ....
    }

    Leaf Class

    public class MenuItem extends MenuComponent {
        String name;
        String description;
        ....
        
        // 예시에서는 이 메서드만 비교
        public void print() {
            System.out.print(" " + getName());
            if (this.isVegetarian()) System.out.println("(v)");
                System.out.println(", " + getPrice());
            System.out.println(" --" + getDescription());
        }
    }

    Composite Class

    • menuComponents에 자식들을 담는다. 
    • print시 자식들을 하나씩 꺼내며 반복
    • 자식을 꺼낼 때 상위 인터페이스의 함수를 호출하므로 타입은 Leaf인지 Composite인지 신경 쓸 필요가 없음 (polymorphism)
    public class Menu extends MenuComponent {
        // 자식들을 담음
        ArraryList menuComponents = new ArrayList();
        String name;
        String description;
        ....
        
        public void print() {
            // 자기 자식을 하나씩 꺼내면서 출력함
            Iterator iterator = menuComponents.iterator();
            while (iterator.hasNext()) {
                // 자식의 타입은 신경 쓸 필요가 없음
                MenuComponent menuComponent = (MenuComponent)iterator.next();
                menuComponent.print();
            }
        }
    }

     

    # 트리 구조를 위한 Iterator

    아래와 같은 트리 구조의 경우 위 코드의 iterator 방식이 작동하지 않는다. 

    만약 전체 트리를 순회하는 Iterator를 만들 수 있다면 아래와 같이 특정 메뉴를 찍는 작업도 할 수 있다. 

    public class Waitress {
        MenuComponent allMenus;
        public Watiress(MenuComponent allMenus) {
            this.allMenus = allMenus;
        }
        public void printMenu() {
            allMenus.print();
        }
        public void printVegetarianMenu() {
            // 전체 트리를 순회하는 Iterator
            Iterator iterator = allMenus.createIterator();
            System.out.println("\nVEGETARIAN MENU\n-------");
            while (iterator.hasNext()) {
                MenuComponent menuComponent = (MenuComponent)iterator.next();
                try {
                    if (menuComponent.isVegetarian()) menuComponent.print();
                } catch (UnsupportedOperationException e) {}
            }
        }
    }

    전체 Iterator를 순회하는 코드는 아래와 같이 작성할 수 있다. 

    • 자식이 있는 Comosite에서는 Iterator를 반환한다. 
    • 자식이 없는 Leaf는 동일한 타입으로 일관성 있게 핸들링하기 위해 NullIterator를 사용한다. 
    public class Menu extends MenuComponent {
        Iterator iterator = null;
        // other code here doesn't change
        public Iterator createIterator() {
            if (iterator == null)
                iterator = new CompositeIterator(menuComponents.iterator());
            return iterator;
        }
    }
    
    public class MenuItem extends MenuComponent {
        // other code here doesn't change
        public Iterator createIterator() {
            return new NullIterator();
        }
    }

     

    # 고려할 점

    add(), remove(), getchild() 같은 자식을 다루는 메서드는 어디에 있어야 적절한가?

    • 어떤 것을 중요시 하냐에 따라 두 가지 옵션이 있다. 

    왼쪽은 위의 예시에서 사용한 방법으로 Transparency를 중요시 하는 구현 방식이다. 

    • 장점은 모든 컴포넌트를 같은 방식으로 다룰 수 있다. 
    • 단점은 Component에서 Leaf에 없는 operation에 접근을 하는 것에 대해 Exception을 구현해줘야 한다. 
      • 컴파일 타임에는 확인할 수가 없고 런타임때 확인할 수 있다. 

    오른쪽은 Safety를 중요시 하는 구현 방식으로 공통된 operation만 Component에 구현한다. 

    • 장점은 Leaf에 없는 operation에 대해 접근하는 것을 컴파일 타임때 탐지할 수 있다. 
    • 단점은 더 이상 Leaf와 composite를 같은 방식으로 접근할 수 없다. 

     

    # 관련된 패턴

    ## Composite vs Decorator

    공통점 - 구조적으론 비슷하다. 

    • 상위 타입으로 자식에 접근하며 recursive하게 만들 수 있다. 

    차이점 - 의도가 다르다. 

    • Decorator Pattern은 이미 존재하는 객체에 새로운 책임을 부여하기 위해 감싼다. 
    • Composite Pattern은 트리 모양의 hierarchy를 표현하는데 초점을 맞추고 있다. 

    둘 다 비슷하므로 혼용하는 경우가 많이 있다. 

     

    ## Iterator Pattern

    아래의 자료구조를 노출하지 않으면서 object의 element에 접근하기 위한 패턴으로 Composite Pattern과 유사하다. 

     

    # 정리

    Composite Pattern은 트리 모양의 구조를 표현하고 전체 계층(hierarchy)를 표현할 수 있다. 

     

    클라이언트는 개별 오브젝트를 구분하지 않고 접근할 수 있다. (Leaf, Composite)

     

    'Learn > Architecture' 카테고리의 다른 글

    [Design Pattern] MVC Pattern  (0) 2022.09.25
    [Design Pattern] Bridge Pattern  (0) 2022.09.25
    [Design Pattern] Decorator Pattern  (0) 2022.09.24
    [Design Pattern] Adapter Pattern  (0) 2022.09.04
    [Design Pattern] Singleton Pattern  (0) 2022.08.31

    댓글

Designed by Tistory.