이 글은 아래 두 글과 함께보면 더 좋습니다.
프록시 패턴
프록시 패턴 프록시 패턴은 특정 객체를 대리하여 작업을 처리하는 프록시 객체를 제공하여 로직의 흐름을 제어하기 위한 패턴이다. 클라이언트와 실제 객체를 중계해주는 대리자 역할을 한다
ppusda.tistory.com
데코레이터 패턴과 프록시 패턴의 차이
이 글은 아래 두 글로 부터 이어집니다. https://ppusda.tistory.com/84 데코레이터 패턴 데코레이터 패턴 데코레이터 패턴은 기존 객체를 변경하지 않고 기능을 동적으로 추가하거나 확장할 수 있게 해
ppusda.tistory.com
한 번에 글을 보고 싶으신 분은 아래를 참고해주세요.
데코레이터 패턴
데코레이터 패턴은 기존 객체를 변경하지 않고 기능을 동적으로 추가하거나 확장할 수 있게 해주는 디자인 패턴이다.
데코레이터의 말 뜻대로 객체를 꾸며주는 역할을 한다고 생각하면 이해하기 쉽다.
구조
아래 그림에 따라 각 요소에 대한 설명과 예제를 통해 설명해보겠다.
Component
Component
는 기능을 정의하는 인터페이스이다.
구현 객체인 ConcreteComponent
와 장식자인 Decorator
가 이를 상속받아 구현하게 된다.
public interface Food {
String name();
int price();
void order();
}
ConcreteComponent
ConcreteComponent
는 인터페이스를 상속받아 실제로 구현한 구현 객체이다.
데코레이팅 될 실제 객체라고 생각하면 된다.
public class Hamburger implements Food {
@Override
public String name() {
return "햄버거";
}
@Override
public int price() {
return 7500;
}
@Override
public void order() {
System.out.println("주문 했습니다");
}
}
Decorator
Decorator
는 추상 클래스 혹은 인터페이스로 정의되어 새로운 기능이나 기존 기능에 기능을 추가하는 메서드를 선언하는 역할을 한다.
상세 기능 추가를 진행하기 위해 ConcreteDecorator
는 ****이를 상속받아 상세 내용을 구현하게 된다.
public abstract class FoodDecorator implements Food { // 추상 클래스로 구현한 경우
private Food food;
public FoodDecorator(Food food) {
this.food = food;
}
@Override
public String name() {
return food.name(); // 기능 위임
}
@Override
public int price() {
return food.price(); // 기능 위임
}
@Override
public void order() {
food.order(); // 기능 위임
}
}
interface FoodDecorator extends Food { // 인터페이스로 구현한 경우
void extraOrder();
}
ConcreteDecorator
ConcreteDecorator
는 추상 클래스 혹은 인터페이스를 상속받아 구체화 한 데코레이터이다.
public class BaconDecorator extends FoodDecorator { // 추상 클래스를 상속받아 구체화 한 데코레이터
public BaconDecorator (Food food) {
super(food);
}
@Override
public String name() {
return "베이컨" + super.name();
}
@Override
public int price() {
return super.price() + 2000;
}
@Override
public void order() {
super.order(); // 원본 객체의 operation() 실행
extraOrder(); // 추가적인 동작
}
public void extraOrder() {
System.out.println("+ 베이컨");
}
}
public class CheeseDecorator extends FoodDecorator { // 추상 클래스를 상속받아 구체화 한 데코레이터
public CheeseDecorator (Food food) {
super(food);
}
@Override
public String name() {
return "치즈" + super.name();
}
@Override
public int price() {
return super.price() + 1000;
}
@Override
public void order() {
super.order(); // 원본 객체의 operation() 실행
extraOrder(); // 추가적인 동작
}
public void extraOrder() {
System.out.println("+ 치즈");
}
}
class BaconDecorator implements FoodDecorator { // 인터페이스를 상속받아 구체화 한 데코레이터
Food food; // 원본 객체를 composition
BaconDecorator(Food food) {
this.food = food;
}
public String name() {
return "베이컨" + food.name();
}
public int price() {
return food.price() + 2000;
}
public void order() {
food.order(); // 원본 객체의 operation() 실행
extraOrder(); // 추가적인 동작
}
public void extraOrder() {
System.out.println("+ 베이컨");
}
}
class CheeseDecorator implements FoodDecorator { // 인터페이스를 상속받아 구체화 한 데코레이터
Food food; // 원본 객체를 composition
CheeseDecorator(Food food) {
this.food = food;
}
public String name() {
return "치즈" + food.name();
}
public int price() {
return food.price() + 1000;
}
public void order() {
food.order(); // 원본 객체의 operation() 실행
extraOrder(); // 추가적인 동작
}
public void extraOrder() {
System.out.println("+ 치즈");
}
}
이를 실제로 실행했을 때, 아래와 같이 동작하게 된다.
public class Main {
public static void main(String[] args) {
Food food = new Hamburger();
FoodDecorator baconBurger = new BaconDecorator(food);
FoodDecorator cheeseBurger = new CheeseDecorator(food);
FoodDecorator baconCheeseBurger = new BaconDecorator(new CheeseDecorator(food));
System.out.println("기존 객체");
System.out.println(food.name() + ", " + food.price() + "원");
food.order();
System.out.println();
System.out.println("베이컨 데코레이터");
System.out.println(baconBurger.name() + ", " + baconBurger.price() + "원");
baconBurger.order();
System.out.println();
System.out.println("치즈 데코레이터");
System.out.println(cheeseBurger.name() + ", " + cheeseBurger.price() + "원");
cheeseBurger.order();
System.out.println();
System.out.println("베이컨 치즈 데코레이터");
System.out.println(baconCheeseBurger.name() + ", " + baconCheeseBurger.price() + "원");
baconCheeseBurger.order();
}
}
기존 객체
햄버거, 7500원
주문 했습니다
베이컨 데코레이터
베이컨햄버거, 9500원
주문 했습니다
+ 베이컨
치즈 데코레이터
치즈햄버거, 8500원
주문 했습니다
+ 치즈
베이컨 치즈 데코레이터
베이컨치즈햄버거, 10500원
주문 했습니다
+ 치즈
+ 베이컨
위와 데코레이터 패턴을 적용했을 경우에는 같이 하나의 객체만 만들어 둔 후 데코레이터를 통해서 기존 객체에 기능을 추가할 수 있다.
그렇기에 핫도그나 샌드위치 같은 새로운 음식이 추가되었을 경우에도 이미 구현된 데코레이터를 통해서 베이컨과 치즈를 추가할 수 있게 되었다.
만약 데코레이터 패턴을 쓰지 않았을 경우에는 추가 기능을 위해 BaconHamburger
와 CheeseHamburger
를 새로운 구현 객체로 만들어서 사용했어야 되었을 것이다.
public class BaconHamburger implements Food {
private Food hamburger;
public BaconHamburger(Food hamburger) {
this.hamburger = hamburger;
}
@Override
public String name() {
return "베이컨" + hamburger.name();
}
@Override
public int price() {
return hamburger.price() + 2000;
}
@Override
public void order() {
hamburger.order();
System.out.println("+ 베이컨");
}
}
public class CheeseHamburger implements Food {
private Food hamburger;
public BaconHamburger(Food hamburger) {
this.hamburger = hamburger;
}
@Override
public String name() {
return "치즈" + hamburger.name();
}
@Override
public int price() {
return hamburger.price() + 1000;
}
@Override
public void order() {
hamburger.order();
System.out.println("+ 치즈");
}
}
이렇게 음식이 구현 객체로 추가되는 방식은 향후 새로운 음식이 만들었을 때도 BaconHotdog
, CheeseHotdog
, BaconSandwitch
, CheeseSandwitch
처럼 구현 객체를 모두 만들어야 하므로 데코레이터를 사용하여 장식하는 것 처럼 유연하게 대처할 수 없다는 것을 알 수 있다.
특징
장점
- 데코레이터를 통한 유연한 기능 확장이 가능하다.
- 컴파일 시점이 아닌 런타임시 동적으로 기능을 변경할 수 있다.
- 여러 데코레이터로 구현 객체를 래핑하여 여러 기능을 결합시킬 수 있다.
- 장식자 클래스마다 고유한 책임을 가지기 때문에 단일 책임 원칙(SRP)을 준수 할 수 있다.
- 직접적인 객체의 코드 수정 없이 기능 확장이 필요할 시 장식자 클래스를 추가해 사용하면 되기에 개방 폐쇄 원칙(OCP)를 준수 할 수 있다.
- 구현 객체가 아닌 인터페이스를 상속받아 구현하므로 의존 역전 원칙(DIP)를 준수 할 수 있다.
단점
- 이미 적용된 데코레이터의 경우 다시 제거하기가 어렵다.
- 런타임 시 기능을 추가하기 때문에 객체를 수정하는 과정에서 성능 저하가 발생할 수 있다.
- 여러 데코레이터를 적용하는 경우 초기화 코드가 복잡해 보일 수 있다.
- ex)
new BaconDecorator(new CheeseDecorator(food));
- ex)
참고
https://inpa.tistory.com/entry/GOF-💠-데코레이터Decorator-패턴-제대로-배워보자
https://ittrue.tistory.com/558
데코레이터 패턴 (refactoring.guru)
'오늘의 공부 > 디자인 패턴' 카테고리의 다른 글
데코레이터 패턴과 프록시 패턴의 차이 (2) | 2024.04.12 |
---|---|
프록시 패턴 (2) | 2024.04.12 |
이 글은 아래 두 글과 함께보면 더 좋습니다.
프록시 패턴
프록시 패턴 프록시 패턴은 특정 객체를 대리하여 작업을 처리하는 프록시 객체를 제공하여 로직의 흐름을 제어하기 위한 패턴이다. 클라이언트와 실제 객체를 중계해주는 대리자 역할을 한다
ppusda.tistory.com
데코레이터 패턴과 프록시 패턴의 차이
이 글은 아래 두 글로 부터 이어집니다. https://ppusda.tistory.com/84 데코레이터 패턴 데코레이터 패턴 데코레이터 패턴은 기존 객체를 변경하지 않고 기능을 동적으로 추가하거나 확장할 수 있게 해
ppusda.tistory.com
한 번에 글을 보고 싶으신 분은 아래를 참고해주세요.
데코레이터 패턴
데코레이터 패턴은 기존 객체를 변경하지 않고 기능을 동적으로 추가하거나 확장할 수 있게 해주는 디자인 패턴이다.
데코레이터의 말 뜻대로 객체를 꾸며주는 역할을 한다고 생각하면 이해하기 쉽다.
구조
아래 그림에 따라 각 요소에 대한 설명과 예제를 통해 설명해보겠다.
Component
Component
는 기능을 정의하는 인터페이스이다.
구현 객체인 ConcreteComponent
와 장식자인 Decorator
가 이를 상속받아 구현하게 된다.
public interface Food {
String name();
int price();
void order();
}
ConcreteComponent
ConcreteComponent
는 인터페이스를 상속받아 실제로 구현한 구현 객체이다.
데코레이팅 될 실제 객체라고 생각하면 된다.
public class Hamburger implements Food {
@Override
public String name() {
return "햄버거";
}
@Override
public int price() {
return 7500;
}
@Override
public void order() {
System.out.println("주문 했습니다");
}
}
Decorator
Decorator
는 추상 클래스 혹은 인터페이스로 정의되어 새로운 기능이나 기존 기능에 기능을 추가하는 메서드를 선언하는 역할을 한다.
상세 기능 추가를 진행하기 위해 ConcreteDecorator
는 ****이를 상속받아 상세 내용을 구현하게 된다.
public abstract class FoodDecorator implements Food { // 추상 클래스로 구현한 경우
private Food food;
public FoodDecorator(Food food) {
this.food = food;
}
@Override
public String name() {
return food.name(); // 기능 위임
}
@Override
public int price() {
return food.price(); // 기능 위임
}
@Override
public void order() {
food.order(); // 기능 위임
}
}
interface FoodDecorator extends Food { // 인터페이스로 구현한 경우
void extraOrder();
}
ConcreteDecorator
ConcreteDecorator
는 추상 클래스 혹은 인터페이스를 상속받아 구체화 한 데코레이터이다.
public class BaconDecorator extends FoodDecorator { // 추상 클래스를 상속받아 구체화 한 데코레이터
public BaconDecorator (Food food) {
super(food);
}
@Override
public String name() {
return "베이컨" + super.name();
}
@Override
public int price() {
return super.price() + 2000;
}
@Override
public void order() {
super.order(); // 원본 객체의 operation() 실행
extraOrder(); // 추가적인 동작
}
public void extraOrder() {
System.out.println("+ 베이컨");
}
}
public class CheeseDecorator extends FoodDecorator { // 추상 클래스를 상속받아 구체화 한 데코레이터
public CheeseDecorator (Food food) {
super(food);
}
@Override
public String name() {
return "치즈" + super.name();
}
@Override
public int price() {
return super.price() + 1000;
}
@Override
public void order() {
super.order(); // 원본 객체의 operation() 실행
extraOrder(); // 추가적인 동작
}
public void extraOrder() {
System.out.println("+ 치즈");
}
}
class BaconDecorator implements FoodDecorator { // 인터페이스를 상속받아 구체화 한 데코레이터
Food food; // 원본 객체를 composition
BaconDecorator(Food food) {
this.food = food;
}
public String name() {
return "베이컨" + food.name();
}
public int price() {
return food.price() + 2000;
}
public void order() {
food.order(); // 원본 객체의 operation() 실행
extraOrder(); // 추가적인 동작
}
public void extraOrder() {
System.out.println("+ 베이컨");
}
}
class CheeseDecorator implements FoodDecorator { // 인터페이스를 상속받아 구체화 한 데코레이터
Food food; // 원본 객체를 composition
CheeseDecorator(Food food) {
this.food = food;
}
public String name() {
return "치즈" + food.name();
}
public int price() {
return food.price() + 1000;
}
public void order() {
food.order(); // 원본 객체의 operation() 실행
extraOrder(); // 추가적인 동작
}
public void extraOrder() {
System.out.println("+ 치즈");
}
}
이를 실제로 실행했을 때, 아래와 같이 동작하게 된다.
public class Main {
public static void main(String[] args) {
Food food = new Hamburger();
FoodDecorator baconBurger = new BaconDecorator(food);
FoodDecorator cheeseBurger = new CheeseDecorator(food);
FoodDecorator baconCheeseBurger = new BaconDecorator(new CheeseDecorator(food));
System.out.println("기존 객체");
System.out.println(food.name() + ", " + food.price() + "원");
food.order();
System.out.println();
System.out.println("베이컨 데코레이터");
System.out.println(baconBurger.name() + ", " + baconBurger.price() + "원");
baconBurger.order();
System.out.println();
System.out.println("치즈 데코레이터");
System.out.println(cheeseBurger.name() + ", " + cheeseBurger.price() + "원");
cheeseBurger.order();
System.out.println();
System.out.println("베이컨 치즈 데코레이터");
System.out.println(baconCheeseBurger.name() + ", " + baconCheeseBurger.price() + "원");
baconCheeseBurger.order();
}
}
기존 객체
햄버거, 7500원
주문 했습니다
베이컨 데코레이터
베이컨햄버거, 9500원
주문 했습니다
+ 베이컨
치즈 데코레이터
치즈햄버거, 8500원
주문 했습니다
+ 치즈
베이컨 치즈 데코레이터
베이컨치즈햄버거, 10500원
주문 했습니다
+ 치즈
+ 베이컨
위와 데코레이터 패턴을 적용했을 경우에는 같이 하나의 객체만 만들어 둔 후 데코레이터를 통해서 기존 객체에 기능을 추가할 수 있다.
그렇기에 핫도그나 샌드위치 같은 새로운 음식이 추가되었을 경우에도 이미 구현된 데코레이터를 통해서 베이컨과 치즈를 추가할 수 있게 되었다.
만약 데코레이터 패턴을 쓰지 않았을 경우에는 추가 기능을 위해 BaconHamburger
와 CheeseHamburger
를 새로운 구현 객체로 만들어서 사용했어야 되었을 것이다.
public class BaconHamburger implements Food {
private Food hamburger;
public BaconHamburger(Food hamburger) {
this.hamburger = hamburger;
}
@Override
public String name() {
return "베이컨" + hamburger.name();
}
@Override
public int price() {
return hamburger.price() + 2000;
}
@Override
public void order() {
hamburger.order();
System.out.println("+ 베이컨");
}
}
public class CheeseHamburger implements Food {
private Food hamburger;
public BaconHamburger(Food hamburger) {
this.hamburger = hamburger;
}
@Override
public String name() {
return "치즈" + hamburger.name();
}
@Override
public int price() {
return hamburger.price() + 1000;
}
@Override
public void order() {
hamburger.order();
System.out.println("+ 치즈");
}
}
이렇게 음식이 구현 객체로 추가되는 방식은 향후 새로운 음식이 만들었을 때도 BaconHotdog
, CheeseHotdog
, BaconSandwitch
, CheeseSandwitch
처럼 구현 객체를 모두 만들어야 하므로 데코레이터를 사용하여 장식하는 것 처럼 유연하게 대처할 수 없다는 것을 알 수 있다.
특징
장점
- 데코레이터를 통한 유연한 기능 확장이 가능하다.
- 컴파일 시점이 아닌 런타임시 동적으로 기능을 변경할 수 있다.
- 여러 데코레이터로 구현 객체를 래핑하여 여러 기능을 결합시킬 수 있다.
- 장식자 클래스마다 고유한 책임을 가지기 때문에 단일 책임 원칙(SRP)을 준수 할 수 있다.
- 직접적인 객체의 코드 수정 없이 기능 확장이 필요할 시 장식자 클래스를 추가해 사용하면 되기에 개방 폐쇄 원칙(OCP)를 준수 할 수 있다.
- 구현 객체가 아닌 인터페이스를 상속받아 구현하므로 의존 역전 원칙(DIP)를 준수 할 수 있다.
단점
- 이미 적용된 데코레이터의 경우 다시 제거하기가 어렵다.
- 런타임 시 기능을 추가하기 때문에 객체를 수정하는 과정에서 성능 저하가 발생할 수 있다.
- 여러 데코레이터를 적용하는 경우 초기화 코드가 복잡해 보일 수 있다.
- ex)
new BaconDecorator(new CheeseDecorator(food));
- ex)
참고
https://inpa.tistory.com/entry/GOF-💠-데코레이터Decorator-패턴-제대로-배워보자
https://ittrue.tistory.com/558
데코레이터 패턴 (refactoring.guru)
'오늘의 공부 > 디자인 패턴' 카테고리의 다른 글
데코레이터 패턴과 프록시 패턴의 차이 (2) | 2024.04.12 |
---|---|
프록시 패턴 (2) | 2024.04.12 |