본문 바로가기

Design Pattern

[디자인 패턴_Again] Composite Pattern.


컴포지트 패턴을 이용하면 객체들을 트리구조로 구성하여 계층구조로 나타낼 수 있습니다.

중요한 것은 트리구조에서 자식이 있는 노드와 자식이 없는 노드를 구분하지 않고

마치 하나처럼 사용 할 수 있다는 것입니다.

자식이 있는 것을 노드라고 부르고, 없는 것을 리프(leaf)라고 부르겠습니다.

컴포지트 패턴에서는 노드와 
리프가 하나의 추상 클래스를 상속받습니다.

노드와 리프를 하나처럼(똑같은 방법) 다루기 위함이죠.. 이 말은 뒤에서 다시 언급해보겠습니다.

쇼핑몰을 위해서 카테고리를 만들려고 합니다.

일단 제일 상위에 "전체 카테고리"가 존재하고 그 밑으로

"의류"와 "전자제품" 카테고리가 있습니다. 그리고 "의류" 밑에는 "점퍼" 라는 하위 카테고리가 존재합니다.



public abstract class CategoryComponent {
 public void add(CategoryComponent component) {
  throw new UnsupportedOperationException();
 }
 
 public CategoryComponent getChild(int i) {
  throw new UnsupportedOperationException();
 }
 
 public void print() {
  throw new UnsupportedOperationException();
 }
 
 public void getDeparName() {
  throw new UnsupportedOperationException();
 }
}


카테고리와 그 밑으로 들어가게 될 상품 모두 이 CategoryComponent를 상속해야 합니다.

그래야 노드와 리프를 같은 객체처럼 다룰 수 있습니다.

그리고 메소드마다 exception을 던지는 이유는, 노드 같은 경우는 자식을 가질 수 있기 때문에 add 메소드를 사용하지만

리프 같은 경우는 자식을 가질 수 없기 때문에 add는 사용하지 않는 메소드입니다.

그래서 상위 클래스인 CategoryComponent에서 위와 같이 구현을 해 놓으면 이 클래스를 상속받는 클래스들은

자신이 사용 할 메소드만 구현하면 됩니다.

그러면 Category를 보겠습니다.


import java.util.ArrayList;
import java.util.Iterator;

public class Category extends CategoryComponent {
 private String name;
 private ArrayList <CategoryComponent> components = new ArrayList<CategoryComponent>();
 
 public Category(String name) {
  this.name = name;
 }
 
 public String getName() {
  return name;
 }
 
 public void add(CategoryComponent component) {
  this.components.add(component);
 }
 
 public CategoryComponent getChild(int i) {
  return components.get(i);
 }
 
 public void print() {
  System.out.println("카테고리 이름 : " + getName());
  System.out.println("-----------------------------");
  Iterator<CategoryComponent> iterator = components.iterator();
  while(iterator.hasNext()) {
   CategoryComponent component = iterator.next();
   component.print();
  } 

 }
}



카테고리는 하위 자식을 가질 수 있습니다. 바로 상품들이죠.. 그리고 카테고리는 하위 카테고리도 자식으로 가질 수 있습니다.

노드나 리프나 모든 CategoryComponent를 상속합니다.

그리고, print를 보시면...

Iterator를 사용해서 재귀호출로 자기가 가진 자식들의 print()를 호출하고 있습니다.

스택을 그려놓고 저 재귀호출이 어떻게 작동하는지 보시는 것도

좋을 것입니다.


public class Product extends CategoryComponent {
 String name;
 String price;
 
 public Product(String name, String price) {
  this.name = name;
  this.price = price;
 }
 
 public String getName() {
  return name;
 }
 
 public String getPrice() {
  return price;
 }
 
 public void print() {
  System.out.println("상품명 : " + getName());
  System.out.println("가격 : " + getPrice());
  System.out.println("-----------------------------");
 }

}

리프를 구성 할 Product 클래스입니다. 마찬가지로, CategoryComponent를 상속받고 있습니다.

그리고 print()메소드에서는 자식이 없기 때문에 그냥 자기 자신의 정보를 print합니다.

위 Category에서 재귀호출로 불려진 print() 메소드는 자기가 가진 최하위 
리프까지 호출되어와서

그 
리프의 print() 메소드부터 다시 실행되며 처리 되는 것입니다.

테스트 클래스입니다.

public class Test {
 public static void main(String[] args) {
  CategoryComponent category = new Category("의류");
  CategoryComponent category2 = new Category("전자제품");
  
  CategoryComponent categoryAll = new Category("전체 카테고리");
  categoryAll.add(category);
  categoryAll.add(category2);
  
  category.add(new Product("스웨터","10000"));
  category.add(new Product("청바지","30000"));
  category.add(new Product("면바지","2000"));
  category.add(new Product("가디건","110000"));
 
  //앗 점퍼 카테고리..
  CategoryComponent category3 = new Category("점퍼");
  category.add(category3);
  category3.add(new Product("지오다노 패딩 점퍼","30000"));
  category3.add(new Product("니 패딩 점퍼","50000"));
  
  category2.add(new Product("아이팟터치","280000"));
  category2.add(new Product("디오스 냉장고","2280000"));
  category2.add(new Product("에어컨","1280000"));
  
  categoryAll.print();
 }
}

트리구조로 상품들을 넣고 있습니다.

전체 밑에 의류와 전자제품이 있고 의류 밑에는 점퍼라는 하위 카테고리가 존재합니다.

그리고 각각의 카테고리는 상품을 가지고 있습니다.

그런데 이것을 모두 출력하는데는 단 한줄의 코드만 있으면 됩니다.

categoryAll.print();

입니다.

카테고리와 상품을 구분해서 출력할 필요도 없습니다.

출력해야 하는 정보는 자기 자신들이 각각 가지고 있을뿐이죠..


사실 Composite 패턴을 사용해서 트리를 제대로 구현하려면 이것보다 더 복잡합니다.