[디자인패턴] 데코레이터패턴

이나겸's avatar
Nov 13, 2024
[디자인패턴] 데코레이터패턴
💡
객체들을 새로운 행동들을 포함한 특수 래퍼 객체들 내에 넣어서 위 행동들을 해당 객체들에 연결시키는 구조적 디자인 패턴 ⇒ 주어진 상황 및 용도에 따라 어떤 객체에 책임을 덧붙임

특징

  • 객체의 결합을 통해 기능을 동적으로 유연하게 확장 가능
  • 객체를 감싸는 방식으로 기능을 추가하거나 변경
    • 기존 코드를 수정하지 않고도 새로운 기능 추가 및 수정 가능 ⇒ 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

Nakyeom's Study