티스토리 뷰

donaricano-btn
반응형

30년 라면 외길 인생을 걸어온 대한민국 최고의 라면 장인 윌슨씨는 최근 생활의 달인에 라면 달인으로 소개되며 유명세를 떨치고 있었습니다. 그 덕분에 그의 라면 가게 역시 개점이래 최대의 호황을 맞고 있었지만 손님이 너무 많이 몰리는 탓에 알바생을 추가로 고용할 수 밖에 없었죠. 그러나 새로운 알바생들은 아직 윌슨씨 가게의 라면 가격을 제대로 외우지 못해 계속해서 계산 실수를 하고 있었습니다.

 

그도 그럴것이 국내 최고의 라면 장인 답게 윌슨씨는 라면에 여러가지 토핑을 추가해 다양한 맛의 라면을 만들고 있었고 그 토핑들의 가격이 각각 달랐기 때문에 처음 일을 시작하는 알바생들이 쉽게 계산할 수 없었기 때문이죠.

 

윌슨씨는 고민끝에 라면의 가격을 자동으로 계산해 주는 프로그램을 제작하기로 결심했고 그가 아는 최고의 개발자인 브래드에게 개발을 의뢰했습니다.

 

윌슨씨의 라면 가게 메뉴판은 다음과 같습니다.

 

라면 토핑
신라면 300 원 치즈 100 원 해물  
진라면 400 원 만두 200 원 소고기  
무파마 500 원 100 원     등...

 

라면은 하나만 선택할 수 있지만 토핑은 여러가지를 선택할 수 있었습니다. 그러니까 치즈, 만두, 떡 세가지 토핑을 선택하고 라면은 신라면을 선택한다면 가격은 700원이 되겠죠. 

 

브래드는 처음에 존재하는 모든 라면을 다 구현해 보려고 했지만 그 경우의 숫자가 너무 많았을 뿐더러 윌슨씨는 새로운 토핑을 추가하거나 장사가 안되는 토핑을 메뉴판에서 뺄 계획을 가지고 있었기 때문에 그럴 때 마다 수십개의 라면 클래스를 더하거나 빼야하는 문제점이 생깁니다.

 

고민하던 브래드는 상속을 이용하면 어떨가 하는 생각을 했습니다. 다음 그림처럼요.

 

라면의 가격을 계산하는 cost() 메소드는 모든 토핑의 유무를 검사한 뒤에 존재하는 토핑의 가격을 모두 더해 값을 구하게 될 것입니다.

 

그러나 브래드는 이 코드의 문제점을 금방 파악했습니다. 새로운 라면이나 토핑이 추가되거나 혹은 삭제되는 경우 또는 토핑의 가격이 바뀌는 경우에는 그 때 마다 기존 코드를 수정해야 합니다. 만약 토핑을 추가할 수 없는 특별한 라면이 새로 추가된다거나, 같은 토핑을 여러개 추가하게 되면 기존의 코드로는 해결하기 힘들 것입니다.

 

이런 경우에 생각해야 할 디자인 원칙이 있습니다. 흔히 OCP(Open-Closed Principle)이라고 불리는 개방 폐쇄의 원칙이 바로 그것입니다. 이 원칙을 간단하게 설명하면 다음과 같습니다.

 

클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다.

 

그러므로 우리는 기존의 코드는 건드리지 않고 확장을 통해서 새로운 행동을 간단하게 추가할 수 있도록 해야 합니다. 이 목표를 달성했을 때 얻을 수 있는 것은 무엇일까요? 새로운 기능을 추가하는 데 있어서 매우 유연해서 급변하는 주변 환경에 잘 적응할 수 있으면서도 강하고 튼튼한 디자인을 만들 수 있겠죠.

 

이 원칙을 다시 우리의 라면값 계산 프로그램에 적용해 보도록 하겠습니다.

 

interface Ramen {
    // ...
    int cost();
}

class ShinRamen implements Ramen {
    // ...
    public int cost() {
        return 300;
    }
}

class JinRamen implements Ramen {
    // ...
    public int cost() {
        return 400;
    }
}

class Mupama implements Ramen {
    // ...
    public int cost() {
        return 500;
    }
}

interface Toping extends Ramen {
    // ...
}

class Cheese implements Toping {

    Ramen ramen;

    public Cheese(Ramen ramen) {
        this.ramen = ramen;
    }

    public int cost() {
        return ramen.cost() + 100;
    }
}

class Dumpling implements Toping {

    Ramen ramen;

    public Dumpling(Ramen ramen) {
        this.ramen = ramen;
    }

    public int cost() {
        return ramen.cost() + 200;
    }
}

class Ddeok implements Toping {

    Ramen ramen;

    public Ddeok(Ramen ramen) {
        this.ramen = ramen;
    }

    public int cost() {
        return ramen.cost() + 100;
    }
}

 

위 코드는 Ramen과 Toping 이라는 인터페이스를 구현하는 라면들과 토핑들의 클래스를 만듭니다. 여기서 중요한 것은 Toping 은 Ramen 을 상속하고 있다는 것과 각각의 토핑 클래스가 Ramen 을 멤버 변수로 소유하고 있고 외부에서 이 라멘 객체를 주입 받는 다는 것입니다.

 

실제로 라면을 주문해 보면 다음과 같습니다.

 

Ramen ramen = new ShinRamen();
ramen = new Cheese(ramen);
ramen = new Dumpling(ramen);
System.out.println(ramen.cost());

 

먼저 신라면 객체를 만들고 만두와 치즈 토핑을 추가합니다. 이 코드를 실행하면 300 + 100 + 200 = 600 원이 나오게 됩니다.

 

이처럼 한 객체를 여러 개의 다른 객체로 감싸서 어떤 행동을 위임하는 패턴을 데코레이터 패턴(Decorator Pattern)이라고 합니다.

 

데코레이터 패턴에서는 객체에 추가적인 요건을 동적으로 첨가합니다. 데코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공합니다.

 

반응형
donaricano-btn
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함