객체들을 새로운 행동들을 포함한 특수 래퍼 객체들 내에 넣어서 위 행동들을 해당 객체들에 연결시키는 구조적 디자인 패턴
⇒ 주어진 상황 및 용도에 따라 어떤 객체에 책임을 덧붙임
특징
- 객체의 결합을 통해 기능을 동적으로 유연하게 확장 가능
- 객체를 감싸는 방식으로 기능을 추가하거나 변경
- 기존 코드를 수정하지 않고도 새로운 기능 추가 및 수정 가능 ⇒ ocp(개방 폐쇄 원칙) 준수
- SRP(단일책임원칙) 준수 ⇒ 하나의 클래스는 하나의 책임을 가짐
- 코드 재사용
- 인터페이스의 구현 활용
- 오버라이드, 동적바인딩
- DIP(의존성 역전 원칙) 준수 ⇒ 구체적인 것(객체)에 의존하지않고 추상적인것(인터페이스, 추상클래스)에 의존
- 데코레이터를 적용하는 순서에 따라 최종적으로 생성되는 객체의 동작이나 상태 달라질 수 있음
예제 1
- interface(Notifier) : 하위 클래스에서 오버라이드해서 구현할 메소드 생성
- 하위 클래스 (구현 클래스) ⇒ 메소드 오버라이드로 인터페이스 구현 ⇒ Notifier에 의존해야해서 Notifier 필드(객체) 선언 ⇒ 의존을 안 할 경우의 기본 생성자와, 의존을 할 경우의 생성자 생성
- EmailNotifier
- BasicNotifier
- SmsNotifier
- App 클래스의 main에서 데코레이터 패턴을 이용한 기능 확장 실행
package ch06.notification;
// 인터페이스
public interface Notifier {
// 하위클래스에서 오버라이드해서 구현할 메소드
// public abstract는 생략되어있음
void send();
}
package ch06.notification;
// 인터페이스 받아서 구현 => 타입 일치시킴
public class EmailNotifier implements Notifier {
// Notifier에 의존해야해서 필드 선언
Notifier notifier;
// 의존을 안 할 경우의 기본 생성자
public EmailNotifier() {
}
// 의존을 할 경우의 생성자
// 파라미터에 의존성 주입(매개 변수)
public EmailNotifier(Notifier notifier) {
this.notifier = notifier;
}
// 인터페이스의 동일 메소드 오버라이드 (어노테이션 없어도 됨)
public void send() {
// notifier가 null이 아닐 경우에만 메소드가 실행되도록
// try-catch로 null예외처리도 가능
if (notifier != null) {
notifier.send();
}
System.out.println("이메일 알림");
}
}
package ch06.notification;
// 인터페이스 받아서 구현 => 타입 일치시킴
public class BasicNotifier implements Notifier {
// Notifier에 의존해야해서 필드 선언
Notifier notifier;
// 의존을 안 할 경우의 기본 생성자
public BasicNotifier() {
}
// 의존을 할 경우의 생성자
// 파라미터에 의존성 주입(매개 변수)
public BasicNotifier(Notifier notifier) {
this.notifier = notifier;
}
// 인터페이스의 동일 메소드 오버라이드 (어노테이션 없어도 됨)
public void send() {
// notifier가 null이 아닐 경우에만 메소드가 실행되도록
// try-catch로 null예외처리도 가능
if (notifier != null) {
notifier.send();
}
System.out.println("기본 알림");
}
}
package ch06.notification;
// 인터페이스 받아서 구현 => 타입 일치시킴
public class SmsNotifier implements Notifier {
// Notifier에 의존해야해서 필드 선언
Notifier notifier;
// 의존을 안 할 경우의 기본 생성자
public SmsNotifier() {
}
// 의존을 할 경우의 생성자
// 파라미터에 의존성 주입(매개 변수)
public SmsNotifier(Notifier notifier) {
this.notifier = notifier;
}
// 인터페이스의 동일 메소드 오버라이드 (어노테이션 없어도 됨)
@Override
public void send() {
// notifier가 null이 아닐 경우에만 메소드가 실행되도록
// try-catch로 null예외처리도 가능
if (notifier != null) {
notifier.send();
}
System.out.println("SMS 알림");
}
}
package ch06;
import ch06.notification.BasicNotifier;
import ch06.notification.EmailNotifier;
import ch06.notification.Notifier;
import ch06.notification.SmsNotifier;
public class App {
// 공통으로 활용할 알림 메소드
public static void 알림(Notifier notifier) {
notifier.send();
}
// 데코레이터 패턴 : 기능 확장 (핵심)
public static void main(String[] args) {
// 1. 전체 알림 (기본 알림 -> 문자 알림 -> 이메일 알림)
Notifier n1 = new EmailNotifier(new SmsNotifier(new BasicNotifier()));
알림(n1);
System.out.println("================");
// 2. 전체 알림 (기본 알림 -> 이메일 알림 -> 문자 알림)
Notifier n2 = new SmsNotifier(new EmailNotifier(new BasicNotifier()));
알림(n2);
System.out.println("================");
// 3. 기본 알림
Notifier n3 = new BasicNotifier();
알림(n3);
System.out.println("================");
// 4. 기본 알림 + 문자 알림
Notifier n4 = new SmsNotifier(new BasicNotifier());
알림(n4);
System.out.println("================");
// 5. 기본 알림 + 이메일 알림
Notifier n5 = new EmailNotifier(new BasicNotifier());
알림(n5);
System.out.println("================");
// 6. 이메일 알림
Notifier n6 = new EmailNotifier();
알림(n6);
System.out.println("================");
// 7. 문자 알림
Notifier n7 = new SmsNotifier();
알림(n7);
System.out.println("================");
// 8. 문자 알림 + 이메일 알림
Notifier n8 = new EmailNotifier(new SmsNotifier());
알림(n8);
}
}예제 2
- interface(Notifier) : 하위 클래스에서 오버라이드해서 구현할 메소드 생성
- Burger
- Material
- 하위 클래스 (구현 클래스) ⇒ 메소드 오버라이드로 인터페이스 구현 ⇒ Notifier에 의존해야해서 Notifier 필드(객체) 선언 ⇒ 의존을 안 할 경우의 기본 생성자와, 의존을 할 경우의 생성자 생성
- CheeseBurger
- ChickenBurger
- Ham
- Sangchu
- Carrot
- App 클래스의 main에서 데코레이터 패턴을 이용한 기능 확장 실행
package lotteria.burger;
// 인터페이스
public interface Burger {
// 하위 클래스에서 오버라이드해서 구현할 메소드
// public abstract는 생략되어있음 (interface니까)
void make();
}
package lotteria.material;
// 인터페이스
public interface Material {
// 하위 클래스에서 오버라이드해서 구현할 메소드
// public abstract는 생략되어있음 (interface니까)
void add();
}
package lotteria.burger;
import lotteria.material.Material;
// 인터페이스(Burger) 구현
public class CheeseBurger implements Burger {
// Material에 의존해야해서 필드 선언
Material material;
// 의존을 안 할 경우의 기본 생성자
public CheeseBurger(){}
// 의존을 할 경우의 생성자
// 파라미터에 의존성 주입(매개 변수)
public CheeseBurger(Material material) {
this.material = material;
}
// Burger 인터페이스의 동일 메소드 오버라이드(어노테이션 생략가능)
public void make(){
System.out.println("치즈버거 생성");
// material이 null이 아닐 경우에만 실행
if(material != null) material.add();
}
}
package lotteria.burger;
import lotteria.material.Material;
// 인터페이스(Burger) 구현
public class ChickenBurger implements Burger {
// Material에 의존해야해서 필드 선언
Material material;
// 의존을 안 할 경우의 기본 생성자
public ChickenBurger(){}
// 의존을 할 경우의 생성자
// 파라미터에 의존성 주입(매개 변수)
public ChickenBurger(Material material) {
this.material = material;
}
// Burger 인터페이스의 동일 메소드 오버라이드(어노테이션 생략가능)
public void make(){
System.out.println("치킨버거 생성");
// material이 null이 아닐 경우에만 실행
if(material != null) material.add();
}
}
package lotteria.material;
// 인터페이스(Material) 구현
public class Ham implements Material{
// Material에 의존해야해서 필드 선언
Material material;
// 의존을 할 경우의 생성자
// 파라미터에 의존성 주입(매개 변수)
public Ham(Material material){
this.material = material;
}
// 의존을 안 할 경우의 기본 생성자
public Ham(){}
// Material 인터페이스의 동일 메소드 오버라이드(어노테이션 생략가능)
@Override
public void add() {
System.out.println("햄 추가");
// material이 null이 아닐 경우에만 실행
if(material != null){
material.add();
}
}
}
package lotteria.material;
// 인터페이스(Material) 구현
public class Sangchu implements Material{
// Material에 의존해야해서 필드 선언
Material material;
// 의존을 할 경우의 생성자
// 파라미터에 의존성 주입(매개 변수)
public Sangchu(Material material){
this.material = material;
}
// 의존을 안 할 경우의 기본 생성자
public Sangchu(){}
// Material 인터페이스의 동일 메소드 오버라이드(어노테이션 생략가능)
@Override
public void add() {
System.out.println("상추 추가");
// material이 null이 아닐 경우에만 실행
if(material != null){
material.add();
}
}
}
package lotteria.material;
// 인터페이스(Material) 구현
public class Carrot implements Material{
// Material에 의존해야해서 필드 선언
Material material;
// 의존을 할 경우의 생성자
// 파라미터에 의존성 주입(매개 변수)
public Carrot(Material material){
this.material = material;
}
// 의존을 안 할 경우의 기본 생성자
public Carrot(){}
// Material 인터페이스의 동일 메소드 오버라이드(어노테이션 생략가능)
@Override
public void add() {
System.out.println("당근 추가");
// material이 null이 아닐 경우에만 실행
if(material != null){
material.add();
}
}
}
package lotteria;
import lotteria.burger.Burger;
import lotteria.burger.CheeseBurger;
import lotteria.burger.ChickenBurger;
import lotteria.material.Carrot;
import lotteria.material.Ham;
import lotteria.material.Sangchu;
// 데코레이터패턴 예제 추가
// 데코레이터 패턴 : 기능 확장 (핵심)
public class App {
public static void main(String[] args) {
// 치즈버거 생성
Burger b1 = new CheeseBurger();
b1.make();
System.out.println("================");
// 치킨버거 생성 + 햄 추가 + 햄 추가 + 상추 추가
Burger b2 = new ChickenBurger(new Ham(new Ham(new Sangchu())));
b2.make();
System.out.println("================");
// 치즈버거 생성 + 당근 추가 + 상추 추가 + 햄 추가 + 당근 추가 + 당근 추가
Burger b3 = new CheeseBurger(new Carrot(new Sangchu(new Ham(new Carrot(new Carrot())))));
b3.make();
}
}Share article