Decorator Pattern

 

객체에 추가 요소를 동적으로 더할 수 있습니다.

데코레이터를 사용하면 서브클래스를 만드는 경우에 비해 훨씬 유연하게 기능을 확장할 수 있습니다.

 


 

설명

 

버거 가게 메뉴를 만드려고 합니다.

버거 단품을 주문할 수 있지만 그 단품에 패티, 치즈 등 재료를 추가할 수도 있습니다.

이렇게 추가 기능을 만들 때 각각의 버거 클래스에 패티 추가, 치즈 추가 등 옵션 기능을 추가해주는 것이 맞을까요?

 

Burger

package cg.park.designpattern.decorator.buger;

public class CheeseBurger extends Burger {
    public CheeseBurger() {
        description = "Basic Burger";
    }//버거 이름
    public double cost() {
        return 6000;
    }//버거 가격
}

 

치즈버거 객체입니다.

현재 구현된 것은 버거 이름과 버거 가격입니다.

옵션 추가 기능을 클래스에 부여한다고 가정해보겠습니다.

 

Burger

package cg.park.designpattern.decorator.buger;

public class CheeseBurger extends Burger {
    public CheeseBurger() {
        description = "Basic Burger";
    }//버거 이름
    public double cost() {
        return 6000;
    }//버거 가격

    //------추가 부분------
    public void addCheese() {};//치즈 추가
    public void addPatty() {};//패티 추가
}

 

치즈 추가와 패티 추가내용을 하단에 만들었습니다.

예를 들어 100개의 버거 클래스가 있다면 모든 클래스에 추가 내용을 만들어줘야 합니다.

그리고 추가하는 옵션이 달라질 때마다 100개의 버거 클래스를 수정해야합니다.

과연 이것이 유지보수에 용이하게 만들어진 것일까요?

이번 Decorator Pattern에서는 옵션의 기능을 따로 분리하여 만들 것입니다.

만들어진 객체 코드를 수정하지 않고 추가 기능을 더할 수 있는 패턴입니다.

 

BurgerOption

package cg.park.designpattern.decorator.bugger;

public abstract class BurgerOption extends Burgger {
    public Burgger burgger;
    public abstract String getDescription();

    public Burgger.Size getSize() {return burgger.getSize();}
}

 

버거의 옵션을 담당할 클래스를 따로 만들어줍니다.

 

 

전체 버거의 구성을 잡아줄 추상 클래스 Burger를 만듭니다.

Burger를 상속받아서 각 버거 클래스를 구현합니다.

BurgerOption 클래스도 만들어줍니다.

BurgerOption 클래스는 각 옵션들의 부모 클래스가 될 것이기 때문에 추상 클래스로 구현합니다.

BurgerOption을 상속받은 각 옵션 클래스들을 구현합니다.

 

구현

 

Burger

package cg.park.designpattern.decorator.bugger;

public abstract class Burger {
    public enum Size { BASIC, LARGE };//버거 사이즈
    Burger.Size size = Size.BASIC;//기본 제공 사이즈
    String description = "Unknown Burger";//버거 설명

    //버거 설명
    public String getDescription() {
        return description;
    }

    //버거 사이즈 변경
    public void setSize(Burger.Size size) {
        this.size = size;
    }

    //버거 사이즈 조회
    public Size getSize() {
        return this.size;
    }

    //버거 가격
    public abstract double cost();

}

 

BasicBurger

package cg.park.designpattern.decorator.bugger;

public class BasicBurger extends Burger {
    
    //버거 설명: 기본 버거
    public BasicBurger() {
        description = "Basic Burger";
    }

    //가격: 5000
    public double cost() {
        return 5000;
    }
}

버거 클래스에 사이즈 변경 및 옵션 기능을 넣지 않았습니다.

버거 외 부가적인 기능은 옵션 클래스로 분리하였습니다.

 

BurgerOption

package cg.park.designpattern.decorator.bugger;

public abstract class BurgerOption extends Burger {
    public Burger burger;//버거 객체 상속
    public abstract String getDescription();//버거 설명

    //버거 사이즈
    public Size getSize() {return burger.getSize();}
}

 

Cheese

package cg.park.designpattern.decorator.bugger;

public class Cheese extends BurgerOption {
    //버거 객체
    public Cheese(Burger burger) {
        this.burger = burger;
    }

    //버거 설명에 Cheese 추가
    public String getDescription() {return burger.getDescription() + ", Cheese";}

    //버거 가격에 Cheese 가격 추가
    public double cost() {return burger.cost() + 500;}
}

 

PcgBurger

package cg.park.designpattern.decorator.bugger;

public class PcgBurger {

    public static void main(String args[]) {
        Burger burger = new BasicBurger();//기본 버거
        System.out.println("[첫 번째 주문] " +
                "\n버거: "+ burger.getDescription()
                +"\n가격: "+ burger.cost()
        );

    }
}

버거 주문 시 결과입니다.

기본 버거뿐 아니라 치즈버거, 치킨버거, 불고기버거와 옵션도 추가해보겠습니다.

 

package cg.park.designpattern.decorator.bugger;

import cg.park.designpattern.decorator.bugger.Burger.Size;

public class PcgBurger {

    public static void main(String args[]) {
        Burger burger = new BasicBurger();//기본 버거
        System.out.println("[첫 번째 주문] " +
                "\n버거: "+ burger.getDescription()
                +"\n가격: "+ burger.cost()
        );

        Burger cheeseBurger = new CheeseBurger();//치즈 버거
        cheeseBurger = new Cheese(cheeseBurger);//치즈 추가
        System.out.println("[두 번째 주문] " +
                "\n버거: "+cheeseBurger.getDescription()
                +"\n가격: "+cheeseBurger.cost()
        );

        Burger bulgogiBurger = new BulgogiBurger();//불고기 버거
        bulgogiBurger.setSize(Size.LARGE);//라지로 변경
        System.out.println("[세 번째 주문] " +
                "\n버거: "+bulgogiBurger.getDescription()
                +"\n가격: "+bulgogiBurger.cost()
        );

        Burger chickenBurger = new ChickenBurger();//치킨 버거
        chickenBurger = new Chicken(chickenBurger);//치킨 추가
        System.out.println("[네 번째 주문] " +
                "\n버거: "+chickenBurger.getDescription()
                +"\n가격: "+chickenBurger.cost()
        );
    }
}

각 주문의 결과를 확인할 수 있습니다.

 


 

후기

 

실무에서 클래스에 기능을 추가하는 일은 많습니다.

기능을 추가할 때 단순하게 생각하면 해당 클래스나 메서드 밑에 기능을 추가하면 됩니다.

하지만 이렇게 만든다면 전체 프로젝트 중 방금 수정한 1개의 클래스를 위해 작업이 진행된 것입니다.

해당 추가 기능에 대해 분석, 설계가 잘 되었다면 다행이지만, 임시 방안으로 만든 내용이라면 추후에 문제가 발생할 수 있습니다.

이런 상황에서 프로젝트에 대한 이해와 기본기가 탄탄하면 보다 좋은 방향으로 기능을 추가할 수 있습니다.

감사합니다.

 

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

+ Recent posts