본문 바로가기

기타/객체지향의 탄생(2013)

객체지향의 탄생- Composite pattern

 

  1. 컴포지트 패턴(Composite)

1. 어느 TV프로그램을 보면 큰 선물박스를 선물받았는데 큰 선물박스를 열면 그안에 약간 작은 박스가 있고, 약간 작은 박스를 열면, 중간 사이즈의 선물박스가 있고, 중간 사이즈 박스를 열면, 조금 작은 박스가 있고 마지막 정말 작은 박스를 열었더니 그안에 뭐가 있었더라..라는 내용이 나오곤 한다. 재미있으면서도 받는 사람에게는 약오르는 상황이다.

 

이 상황을 어플리케이션으로 표현한다면 생각나는대로 구현했을때 일단 큰박스, 중간 박스, 작은 박스의 종류를 따져서 작은 박스가 중간박스에 담기게끔 소스를 구현할지도 모른다.

 

[박스의 역할이 작은박스, 중간박스, 큰박스로 고정되어 있다.]

 

public class BigBox {

    List middleBoxs = new ArrayList();

    String title = "큰 박스";

    

    public void addMiddleBox(MiddleBox mBox) {

        System.out.println(title+"에 "+mBox.title+" 를 넣습니다.");

        middleBoxs.add(mBox);

    }

} 

[큰박스는 중간 박스를 담는다.]

 

public class MiddleBox {

    List smallBoxs = new ArrayList();

    String title = "중간 박스";

    

    public void addSmallBox(SmallBox sBox) {

        System.out.println(title+"에 "+sBox.title+" 를 넣습니다.");

        smallBoxs.add(sBox);

    }

 

} 

[중간박스는 작은 박스만을 담는다.]

 

public class SmallBox {

    String title = "작은 박스";

    

} 

[작은박스]

 

public class Main {

 

    public static void main(String[] args) {

        BigBox bigBox = new BigBox();

        MiddleBox middleBox = new MiddleBox();

        SmallBox smallBox = new SmallBox();

        

        bigBox.addMiddleBox(middleBox);

        middleBox.addSmallBox(smallBox);

    }

 

} 

[큰박스에 중간 박스를 담고 중간박스에 작은박스를 담는다.]

 

큰 박스에 중간 박스 를 넣습니다.

중간 박스에 작은 박스 를 넣습니다.

[실행 결과]

 

하지만 이 방법은 박스가 사이즈별로 정해져 있기 때문에 경직되어 있다. 박스 관련 클래스들을 유연하게 변경하거나 확장할 수 없다. 딱 고정된 역할만 수행한다.

 

2. 이터레이션 패턴 편에서 물류분류시스템이 해결되어서 물류센터 직원들이 편해진것에 물류 어플리케이션 개발자는 한숨을 돌리고 있었다. 그러던 어느날 택배기사가 이 문제도 해결해달라며 하소연을 한다.

 

쇼핑몰마다의 물류분류시스템은 통일이 되었지만 지역별로 배달이 할당된 택배기사에게 해당지역에 알맞는 물류를 배정하기가 쉽지 않다는 것이다.

 

자세한 사정을 들어보았다. 여러 쇼핑몰에서 박스들이 오면 이 박스를 해당 지역 택배기사에게 전달해야 하는데 이 과정이 체계화가 되어있지 않아서 그 많은 여러 쇼핑몰의 박스를 확인하여 분류하는데 애를 먹고 있다는 것이다.

 

3.물류 어플리케이션 개발자는 고민끝에 다음과 같은 공정을 고안했다. 일단 쇼핑몰에서 오는 박스를 시구동단위중 동단위로 분류하여 박스를 그룹지어 묶는다. 다음 동단위로 묶은 박스그룹을 다시 구단위로 그룹지어 묶는다. 구단위의 박스 그룹은 다시 시단위의 박스그룹으로 묶는다.

 

다음 시단위별 택배기사를 집합시킨다음 다시 구 단위 동 단위로 나뉘게 한다. 그래서 해당 지역별 택배기사에게 박스를 일괄 지급한다.

 

 

물류 어플리케이션 개발자는 이렇게 깔끔하게 물류지급공정 시스템을 구축하여 문제를 해결했다.

 

[컴포지트 예제 코드 UML]

 

public abstract class TacbaeComponent {

 

    /*************** 박스 인터페이스 *************/

    public String getBoxInfoJuso() {

        throw new UnsupportedOperationException();

    }

    

    public String getBoxInfoProduct() {

        throw new UnsupportedOperationException();

    }

 

    public String getBoxInfoEtc() {

        throw new UnsupportedOperationException();

    }

    

    /*************** 박스 그룹 인터페이스 *************/

    public void add(TacbaeComponent tacbaeComponent) {

        throw new UnsupportedOperationException();        

    }

 

    public void remove(TacbaeComponent tacbaeComponent) {

        throw new UnsupportedOperationException();        

    }

 

    public void getChild(int i) {

        throw new UnsupportedOperationException();        

    }

 

    /*************** 공통 *************/

    public void printDescription() {

        throw new UnsupportedOperationException();                

    }

}

[택배 컴포넌트 수퍼 클래스를 생성한다.]

 

public class TacbaeBox extends TacbaeComponent {

    String juso = "";

    String product = "";

    String etcInfo = "";

    

    public TacbaeBox(String juso, String product, String etcInfo) {

        this.juso = juso;

        this.product = product;

        this.etcInfo = etcInfo;

    }

 

    public String getBoxInfoJuso() {

        return juso;

    }

    

    public String getBoxInfoProduct() {

        return product;

    }

 

    public String getBoxInfoEtc() {

        return etcInfo;

    }

    

    public void printDescription() {

        System.out.println("juso ["+juso+"]");

        System.out.println("product ["+product+"]");

        System.out.println("etcInfo ["+etcInfo+"]");

        System.out.println("");

    }

} 

[택배박스 클래스를 생성한다. 순수 택배 박스와 관련된 메소드만 오버라이드 한다.]

 

public class TacbaeBoxGroup extends TacbaeComponent {

    List tacbaeGroup = new ArrayList();

    String groupTitle = "";

    

    public TacbaeBoxGroup(String groupTitle) {

        this.groupTitle = groupTitle;

    }

    

    public void add(TacbaeComponent tacbaeComponent) {

        tacbaeGroup.add(tacbaeComponent);    

    }

 

    public void remove(TacbaeComponent tacbaeComponent) {

        tacbaeGroup.remove(tacbaeComponent);

    }

    public void getChild(int i) {

        tacbaeGroup.get(i);

    }

    

    public void printDescription() {

        System.out.println("groupTitle ["+groupTitle+"]");

        

        Iterator iterator = tacbaeGroup.iterator();

        while(iterator.hasNext()) {

            TacbaeComponent tacbaeBox = (TacbaeComponent)iterator.next();

            tacbaeBox.printDescription();

        }

    }

}

[택배 박스 그룹 클래스를 생성한다.]

 

public class Main {

    public static void main(String[] args) {

        // 택배 박스들을 생성한다.

        TacbaeComponent tacbaeBox_seoul1 = new TacbaeBox("서울", "아이패드 미니", "기타 정보");

        TacbaeComponent tacbaeBox_seoul2 = new TacbaeBox("서울", "모니터", "기타 정보");

        TacbaeComponent tacbaeBox_seoul3 = new TacbaeBox("서울", "책", "기타 정보");

        

        TacbaeComponent tacbaeBox_buchon1 = new TacbaeBox("부천", "가습기", "기타 정보");

        TacbaeComponent tacbaeBox_buchon2 = new TacbaeBox("부천", "전공 책", "기타 정보");

        TacbaeComponent tacbaeBox_buchon3 = new TacbaeBox("부천", "USB", "기타 정보");

        

        TacbaeComponent tacbaeBox_busan1 = new TacbaeBox("부산", "면도기", "기타 정보");

        TacbaeComponent tacbaeBox_busan2 = new TacbaeBox("부산", "베스트셀러 책", "기타 정보");

        TacbaeComponent tacbaeBox_busan3 = new TacbaeBox("부산", "게임 시디", "기타 정보");

        

        // 택배 박스를 지역별로 분리하여 저장할 그룹을 생성한다.

        TacbaeComponent tacbaeGroup_seoul = new TacbaeBoxGroup("서울 그룹");

        TacbaeComponent tacbaeGroup_buchon = new TacbaeBoxGroup("부천 그룹");

        TacbaeComponent tacbaeGroup_busan = new TacbaeBoxGroup("부산 그룹");

 

        TacbaeComponent tacbaeGroup_all = new TacbaeBoxGroup("전체 그룹");

        

        // 택배 박스를 지역별로 저장한다.

        tacbaeGroup_seoul.add(tacbaeBox_seoul1);

        tacbaeGroup_seoul.add(tacbaeBox_seoul2);

        tacbaeGroup_seoul.add(tacbaeBox_seoul3);

 

        tacbaeGroup_buchon.add(tacbaeBox_buchon1);

        tacbaeGroup_buchon.add(tacbaeBox_buchon2);

        tacbaeGroup_buchon.add(tacbaeBox_buchon3);

 

        tacbaeGroup_busan.add(tacbaeBox_busan1);

        tacbaeGroup_busan.add(tacbaeBox_busan2);

        tacbaeGroup_busan.add(tacbaeBox_busan3);

 

        tacbaeGroup_all.add(tacbaeGroup_seoul);

        tacbaeGroup_all.add(tacbaeGroup_buchon);

        tacbaeGroup_all.add(tacbaeGroup_busan);

        

        tacbaeGroup_all.printDescription();

    }

}

[실행하기]

 

groupTitle [전체 그룹]

groupTitle [서울 그룹]

juso [서울]

product [아이패드 미니]

etcInfo [기타 정보]

 

juso [서울]

product [모니터]

etcInfo [기타 정보]

 

juso [서울]

product [책]

etcInfo [기타 정보]

 

groupTitle [부천 그룹]

juso [부천]

product [가습기]

etcInfo [기타 정보]

 

juso [부천]

product [전공 책]

etcInfo [기타 정보]

 

juso [부천]

product [USB]

etcInfo [기타 정보]

 

groupTitle [부산 그룹]

juso [부산]

product [면도기]

etcInfo [기타 정보]

 

juso [부산]

product [베스트셀러 책]

etcInfo [기타 정보]

 

juso [부산]

product [게임 시디]

etcInfo [기타 정보]

 

코드를 보면 택배용 박스는 인터페이스가 통일되어 있어 어떤 택배 박스라도 그룹을 지어 묶을 있다. 택배 박스와 택배 박스 그룹은 같은 인터페이스를 상속받고 있다. 그래서 박스들을 어느 그룹이든 넣을 있다. 그룹은 다른 그룹에 넣어질 있다. 유연하고 확장하기 편한 좋은 구성을 갖고 있다.

 

4. 컴포지트 패턴은 순수 기능만 수행하는 개별 객체와 자신이 속하는 객체 패밀리의 객체를 담을 수 있는 복합 객체를 통해 트리 구조의 자료구조를 만들게 된다.

 

 

컴포지트 패턴으로 자료구조를 구성하면 개별객체와 복합객체를 똑같이 다루게 되고 재귀적으로 객체들을 호출하기 때문에 클라이언트가 개별객체와 복합객체를 코드상으로 구분할 필요가 없이 간단한 코드만으로도 객체의 추가, 수정, 삭제, 검색등의 다양한 조작을 편리하게 할 수 있다.

 

만약 컴포지트 패턴을 쓰지 않았다면 클라이언트는 여러 객체에 복잡하게 의존했을 것이고 코드 내부가 if문으로 지저분하게 도배 됐을 것이다.

 

[컴포지트 패턴 원본]

 

 

덧 ) 이 객체지향의 탄생 원고는 제가 책으로 내려다가 일단 잘 안되었는데요. 이유는 비문이 많다. 단락내 주제가 중복된다. 어떤 상황 설명을 과장한다.등 입니다. 그래도 원고를 일단 블로그에 몽땅 풀어보고 언젠가 제대로 교정해서 다시 도전할 생각입니다. 이점을 감안해서 읽고 객체지향을 이해하는데 도움이 되셨으면 좋겠습니다. 의견도 주셨으면 좋겠습니다. 원고 조금만 교정하면 괜찮을것 같은 출판사 관계자분의 피드백도 환영합니다. 매주 월요일 발행 예정입니다.