[디자인 패턴] Decorator 패턴

2024. 9. 10. 22:52·Java

들어가기에 앞서

최근 사내에서 진행 중인 타입스크립트 스터디에서 데코레이터 패턴을 다룰 기회가 있었습니다. 자바스크립트는 함수형 언어로서 객체지향적인 패턴을 적용하는 경우가 상대적으로 적기 때문에, 이 패턴을 이해하는 데 어려움이 있을 수 있습니다. 저는 자바 개발자로서, 자바의 객체지향적인 관점에서 데코레이터 패턴을 풀어서 설명해주기 위해 이 글을 작성하게 되었습니다.

상속을 통한 기능 확장

햄버거를 예제 코드로 작성해 보았습니다.


interface Burger {
    String getDescription(); 
    double getCost();        
}

class BasicBurger implements Burger {
    @Override
    public String getDescription() {
        return "Basic Burger";  
    }

    @Override
    public double getCost() {
        return 5.0; 
    }
}

class CheeseBurger extends BasicBurger {
    @Override
    public String getDescription() {
        return "Cheese Burger"; 
    }

    @Override
    public double getCost() {
        return 6.5; 
    }
}

class BaconCheeseBurger extends CheeseBurger {
    @Override
    public String getDescription() {
        return "Bacon Cheese Burger";  
    }

    @Override
    public double getCost() {
        return 8.0;
    }
}

class DoubleBaconCheeseBurger extends BaconCheeseBurger {
    @Override
    public String getDescription() {
        return "Double Bacon Cheese Burger"; 
    }

    @Override
    public double getCost() {
        return 10.0; 
    }
}

public class Main {
    public static void main(String[] args) {
        Burger burger = new DoubleBaconCheeseBurger();

        System.out.println(burger.getDescription() + " $" + burger.getCost());
    }
}

설명:

  • Burger 인터페이스는 getDescription()과 getCost() 메서드를 정의하여 모든 햄버거 클래스가 구현해야 하는 공통 기능을 제공합니다.
  • BasicBurger는 기본 햄버거를 나타내며, 햄버거의 기본적인 기능을 제공합니다.
  • CheeseBurger는 BasicBurger를 상속받아 치즈가 추가된 햄버거를 구현합니다.
  • BaconCheeseBurger는 CheeseBurger를 상속받아 베이컨이 추가된 치즈 버거를 구현합니다.
  • DoubleBaconCheeseBurger는 BaconCheeseBurger를 상속받아 패티가 두 개 추가된 베이컨 치즈 버거를 구현합니다.

상속의 단점

  • 새로운 기능을 추가할 때 마다 계속해서 고유 클래스를 추가해야하는 문제가 생깁니다. 이런 구조는 클래스의 수를 늘리고, 코드의 유지보수를 어렵게 합니다.

데코레이터 패턴

데코레이터 패턴은 객체에 기능확장이 필요할 때 사용되는 패턴입니다. 데코레이터 패턴은 각 기능 독립된 클래스로 만들어, 상속의 남용 없이 조합할 수 있게 해줍니다.

데코레이터 패턴을 적용

interface Burger {
    String getDescription(); 
    double getCost();        
}

class BasicBurger implements Burger {
    @Override
    public String getDescription() {
        return "Basic Burger";  
    }

    @Override
    public double getCost() {
        return 5.0;  
    }
}

abstract class BurgerDecorator implements Burger {
    protected Burger decoratedBurger;  

    public BurgerDecorator(Burger burger) {
        this.decoratedBurger = burger;
    }

    @Override
    public String getDescription() {
        return decoratedBurger.getDescription();  // 기본 버거의 설명을 가져옴
    }

    @Override
    public double getCost() {
        return decoratedBurger.getCost();  // 기본 버거의 가격을 가져옴
    }
}

class Cheese extends BurgerDecorator {
    public Cheese(Burger burger) {
        super(burger);
    }

    @Override
    public String getDescription() {
        return decoratedBurger.getDescription() + ", Cheese";  // 치즈 추가 설명
    }

    @Override
    public double getCost() {
        return decoratedBurger.getCost() + 1.5;  // 치즈 추가 가격
    }
}

class Bacon extends BurgerDecorator {
    public Bacon(Burger burger) {
        super(burger);
    }

    @Override
    public String getDescription() {
        return decoratedBurger.getDescription() + ", Bacon";  // 베이컨 추가 설명
    }

    @Override
    public double getCost() {
        return decoratedBurger.getCost() + 2.0;  // 베이컨 추가 가격
    }
}

class DoublePatty extends BurgerDecorator {
    public DoublePatty(Burger burger) {
        super(burger);
    }

    @Override
    public String getDescription() {
        return decoratedBurger.getDescription() + ", Double Patty";  // 추가 패티 설명
    }

    @Override
    public double getCost() {
        return decoratedBurger.getCost() + 3.0;  // 추가 패티 가격
    }
}

public class Main {
    public static void main(String[] args) {
        Burger burger = new BasicBurger();
        burger = new Cheese(burger);    // 치즈 추가
        burger = new Bacon(burger);     // 베이컨 추가
        burger = new DoublePatty(burger); // 더블 패티 추가


        System.out.println(burger.getDescription() + " $" + burger.getCost());
    }
}

개선된 코드 설명:

  1. Burger 인터페이스: 햄버거의 기본 기능인 getDescription()과 getCost()를 정의합니다.
  2. BasicBurger 클래스: 기본 햄버거의 기능을 구현합니다.
  3. BurgerDecorator 추상 클래스: 데코레이터 클래스의 기본 구조로, 다른 데코레이터들이 상속받을 수 있도록 합니다. 이 클래스는 기본 햄버거 객체를 감싸서 추가 기능을 제공합니다.
  4. Cheese, Bacon, DoublePatty 데코레이터 클래스: 각 데코레이터 클래스는 버거에 특정 재료(치즈, 베이컨, 더블 패티)를 동적으로 추가합니다.

장점:

  • 기능을 추가할 때마다 새로운 클래스를 만들 필요가 없습니다.
  • 여러 기능을 조합하기 위해 상속 계층을 무한히 늘리지 않고, 데코레이터 클래스를 사용하여 다양한 조합을 손쉽게 구현할 수 있습니다.
  • 개방-폐쇄 원칙 준수: 새로운 기능을 추가할 때 기존 클래스는 수정하지 않고 새로운 데코레이터 클래스를 만들기만 하면 됩니다.

대표적인 예시

  • 백준에서 입력을 받을 때 사용하는 io 클래스도 데코레이터 패턴이다.

import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        // InputStreamReader를 사용해 System.in을 감싸고, 그 위에 BufferedReader를 추가
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));


    }
}

'Java' 카테고리의 다른 글

서블릿 필터  (0) 2024.02.05
[실습] 가상스레드 - java  (0) 2024.01.22
가상스레드  (0) 2024.01.21
'Java' 카테고리의 다른 글
  • 서블릿 필터
  • [실습] 가상스레드 - java
  • 가상스레드
HD9504
HD9504
  • HD9504
    습관
    HD9504
  • 전체
    오늘
    어제
    • 분류 전체보기
      • python
        • 트러블슈팅
        • Numpy
        • pandas
        • Wordbook in python
      • Listen to a lecture
        • school for ai in edwith
      • 용어정리
      • 전공 복습
        • 실험계획법
        • 회귀분석
        • 베이지안
        • 일반화 선형모형
      • 자연어처리
      • 글쓰기 공부
      • 밑바닥부터 시작
      • Java
      • Spring
        • JPA
        • Version
      • Web
        • HTML, CSS
        • Javascript
        • 개념, 이론
      • 하루일과
      • 알고리즘 공부
      • 사색
      • 문제해결
      • Database
        • Redis
      • Computer Science
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Decorator
    99클럽
    til
    디자인패턴
    항해99
    코딩테스트준비
    Java
    REDIS
    spring boot
    개발자취업
    spring
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
HD9504
[디자인 패턴] Decorator 패턴
상단으로

티스토리툴바