diff --git "a/\352\265\254\354\241\260/5\354\243\274\354\260\250-\353\215\260\354\275\224\353\240\210\354\235\264\355\204\260/summary/example-code.md" "b/\352\265\254\354\241\260/5\354\243\274\354\260\250-\353\215\260\354\275\224\353\240\210\354\235\264\355\204\260/summary/example-code.md" new file mode 100644 index 0000000..2e471f5 --- /dev/null +++ "b/\352\265\254\354\241\260/5\354\243\274\354\260\250-\353\215\260\354\275\224\353\240\210\354\235\264\355\204\260/summary/example-code.md" @@ -0,0 +1,136 @@ +# 데코레이터 패턴 적용 예제 + +데코레이터 패턴의 개념은 이전 글에서 다루었기 때문에 생략했다. + +- 이전 글 : [https://dev-youngjun.tistory.com/213](https://dev-youngjun.tistory.com/213) + +이번 글은 실무에서는 어떤식으로 데코레이터 패턴이 적용되는지 작성했다. + +## 1. 실무 요구사항 + +채팅 서비스를 도입하는데, 광고 메시지는 보내지 않았으면 좋겠고 욕설은 필터링이 되었으면 좋겠다. + +이를 데코레이터 패턴으로 구현해보자. + +## 2. Decorator 패턴 적용하기 + + +![decorator.png](https://user-images.githubusercontent.com/42997924/159971013-8f0009d2-b9f5-46e6-b97f-158e16e0b3a5.png) + +클라이언트(채팅 입력 주체)는 sendMessage를 하는 목적이 중요하고, 코드는 변경되지 않아야 한다. + +```java +// 채팅 입력 클라이언트(변경x) +public class Client { + private final ChatService chatService; + public Client(ChatService chatService) { + this.chatService = chatService; + } + // 채팅 메시지를 발송 + public void sendMessage(String message) { + chatService.sendMessage(message); + } +} +``` + +기본 컴포넌트(ChatService)와 기본 컴포넌트 구현체(DefaultChatService)는 다음과 같다. + +```java +// Component 인터페이스 +public interface ChatService { + void sendMessage(String message); +} + +// ConcreteComponent : 기본 메시지 발송 +public class DefaultChatService implements ChatService { + // 단 하나의 컴포넌트(ChatService)를 포함 + @Override + public void sendMessage(String message) { + System.out.println("send message : " + message); + } +} +``` + +여기에 자주 추가/삭제될 수 있는 필터링 기능을 Decorator로 구현하면 된다. + +```java +// Decorator : 기능 추가 후 Component 호출 +public class ChatDecorator implements ChatService { + private final ChatService chatService; + public ChatDecorator(ChatService chatService) { + this.chatService = chatService; + } + @Override + public void sendMessage(String message) { + chatService.sendMessage(message); + } +} + +// 광고 필터링 Decorator +public class AdFilterChatDecorator extends ChatDecorator { + public AdFilterChatDecorator(ChatService chatService) { + super(chatService); + } + + @Override + public void sendMessage(String message) { + // 광고 필터링 기능 추가 + if (isNotAd(message) ) { + super.sendMessage(message); + } + } + + private boolean isNotAd(String message) { + return !message.contains("광고"); + } +} + +// 욕설 필터링 Decorator +public class AbuseFilterChatDecorator extends ChatDecorator { + public AbuseFilterChatDecorator(ChatService chatService) { + super(chatService); + } + + @Override + public void sendMessage(String message) { + // 욕설 필터링 기능 추가 + super.sendMessage(abuseFilter(message)); + } + + private String abuseFilter(String message) { + return message.replace("똥개야","이쁜진돗개순종아" ); + } +} +``` + +이제 채팅App에서는 런타임 시 동적으로 플래그 값에 따라 필터를 추가할 수 있다! + +```java +// 채팅 App +public class ChatApp { + private static final boolean enabledAdFilter = true; + private static final boolean enabledAbuseFilter = true; + + public static void main(String[] args) { + // 기본 채팅 서비스 생성 + ChatService chatService = new DefaultChatService(); + // 런타임 시 플래그 값에 따라 필터를 추가 + if (enabledAdFilter) { // 광고 제거 필터 + chatService = new AdFilterChatDecorator(chatService); + } + if (enabledAbuseFilter) { // 욕설 치환 필터 + chatService = new AbuseFilterChatDecorator(chatService); + } + + Client client = new Client(chatService); + client.sendMessage("안녕하세요!"); + client.sendMessage("(광고) 돈이 복사가 된다고?"); + client.sendMessage("똥개야 답장해줘"); + } +} + +/* 실행 결과 +send message = 안녕하세요! +send message = 이쁜진돗개순종아 답장해줘 + */ +``` \ No newline at end of file diff --git "a/\352\265\254\354\241\260/5\354\243\274\354\260\250-\353\215\260\354\275\224\353\240\210\354\235\264\355\204\260/summary/\353\215\260\354\275\224\353\240\210\354\235\264\355\204\260 \355\214\250\355\204\264.md" "b/\352\265\254\354\241\260/5\354\243\274\354\260\250-\353\215\260\354\275\224\353\240\210\354\235\264\355\204\260/summary/\353\215\260\354\275\224\353\240\210\354\235\264\355\204\260 \355\214\250\355\204\264.md" new file mode 100644 index 0000000..39c6a85 --- /dev/null +++ "b/\352\265\254\354\241\260/5\354\243\274\354\260\250-\353\215\260\354\275\224\353\240\210\354\235\264\355\204\260/summary/\353\215\260\354\275\224\353\240\210\354\235\264\355\204\260 \355\214\250\355\204\264.md" @@ -0,0 +1,190 @@ +# 데코레이터 패턴 + +> 기존 코드를 변경하지 않고 부가 기능을 동적으로(유연하게) 추가하는 패턴 + +![](https://refactoring.guru/images/patterns/diagrams/decorator/structure.png) + +**Component**: wrapper들과 wrap된 객체들을 위한 공통 인터페이스 + +**Base Decorator**: Wrapped객체를 참조를 위해서 필드를 가지고 있는 클래스. 베이스 데코레이터는 모든 작업을 Wrapped된 객체에 위임해주는 역할을 한다. + +**Concrete Decorators**: 컴포넌트에 동적으로 추가할 수 있는 추가적인 동작을 정의해두면 .. 이 데코레이터들은 기본 데코레이터의 메서드를 제정의하고 상위 메소드를 부르거나 부르기 전에 이 동작을 실행 시킬 수 있다. + +상속이 아닌 위임을 사용해서 보다 유연하게(**런타임에**) 부가 기능을 추가하는 것도 가능하다. 기능 확장이 필요할 때 상속대신 사용할 수 있는 패턴. + +## 데코레이터 언제 사용되는가? + +- 클래스의 요소들을 계속 수정해서 사용하는 구조 + +- 상황에 따라 다양한 기능이 빈번하게 추가/삭제되는 경우 + +- 객체의 결합을 통해 기능이 생성될 수 있는 경우 + +## 데코레이터 패턴 예제 코드 + +## 1. 실무 요구사항 + +채팅 서비스를 도입하는데, 광고 메시지는 보내지 않았으면 좋겠고 욕설은 필터링이 되었으면 좋겠다. + +이를 데코레이터 패턴으로 구현해보자. + +## 2. Decorator 패턴 적용하기 + +![decoratorpng](https://user-images.githubusercontent.com/42997924/159971013-8f0009d2-b9f5-46e6-b97f-158e16e0b3a5.png) + +클라이언트(채팅 입력 주체)는 sendMessage를 하는 목적이 중요하고, 코드는 변경되지 않아야 한다. + +```java +// 채팅 입력 클라이언트(변경x) +public class Client { + private final ChatService chatService; + public Client(ChatService chatService) { + this.chatService = chatService; + } + // 채팅 메시지를 발송 + public void sendMessage(String message) { + chatService.sendMessage(message); + } +} +``` + +기본 컴포넌트(ChatService)와 기본 컴포넌트 구현체(DefaultChatService)는 다음과 같다. + +```java +// Component 인터페이스 +public interface ChatService { + void sendMessage(String message); +} + +// ConcreteComponent : 기본 메시지 발송 +public class DefaultChatService implements ChatService { + // 단 하나의 컴포넌트(ChatService)를 포함 + @Override + public void sendMessage(String message) { + System.out.println("send message : " + message); + } +} +``` + +여기에 자주 추가/삭제될 수 있는 필터링 기능을 Decorator로 구현하면 된다. + +```java +// Decorator : 기능 추가 후 Component 호출 +public class ChatDecorator implements ChatService { + private final ChatService chatService; + public ChatDecorator(ChatService chatService) { + this.chatService = chatService; + } + @Override + public void sendMessage(String message) { + chatService.sendMessage(message); + } +} + +// 광고 필터링 Decorator +public class AdFilterChatDecorator extends ChatDecorator { + public AdFilterChatDecorator(ChatService chatService) { + super(chatService); + } + + @Override + public void sendMessage(String message) { + // 광고 필터링 기능 추가 + if (isNotAd(message) ) { + super.sendMessage(message); + } + } + + private boolean isNotAd(String message) { + return !message.contains("광고"); + } +} + +// 욕설 필터링 Decorator +public class AbuseFilterChatDecorator extends ChatDecorator { + public AbuseFilterChatDecorator(ChatService chatService) { + super(chatService); + } + + @Override + public void sendMessage(String message) { + // 욕설 필터링 기능 추가 + super.sendMessage(abuseFilter(message)); + } + + private String abuseFilter(String message) { + return message.replace("똥개야","이쁜진돗개순종아" ); + } +} +``` + +이제 채팅App에서는 런타임 시 동적으로 플래그 값에 따라 필터를 추가할 수 있다! + +```java +// 채팅 App +public class ChatApp { + private static final boolean enabledAdFilter = true; + private static final boolean enabledAbuseFilter = true; + + public static void main(String[] args) { + // 기본 채팅 서비스 생성 + ChatService chatService = new DefaultChatService(); + // 런타임 시 플래그 값에 따라 필터를 추가 + if (enabledAdFilter) { // 광고 제거 필터 + chatService = new AdFilterChatDecorator(chatService); + } + if (enabledAbuseFilter) { // 욕설 치환 필터 + chatService = new AbuseFilterChatDecorator(chatService); + } + + Client client = new Client(chatService); + client.sendMessage("안녕하세요!"); + client.sendMessage("(광고) 돈이 복사가 된다고?"); + client.sendMessage("똥개야 답장해줘"); + } +} + +/* 실행 결과 +send message = 안녕하세요! +send message = 이쁜진돗개순종아 답장해줘 + */ +``` + +## 패턴의 장/단점 + +✅ 장점: + +- OCP(개방/폐쇄원칙) 새로운 구독자 클래스를 발행자의 코드 변경 없이 추가 가능. 그리고 그 반대로 발행자 인터페이스가 있다는 가정하에 역도 성립한다. +- 새로운 서브클래스 생성없이, 객체의 행동을 확장 할 수 있다. +- 런타임 도중에 객체로부터 책임을 추가하거나, 제거할 수 있다. +- 여러개의 데코레이터로 갑싸진 객체에 대한 여러개의 동작을 합칠 수 있다. +- SRP(단일 책임 원칙) 여러가지 가능한 변형 동작을 구현한 모놀로식 클래스에 대해서 여러개의 작은 클래스로 나눌 수 있다. + +🚨 단점: + +- Wrapper들의 모음에서 특정 wrapper만 제거 하는건 어렵다. +- 데코레이터들의 모음에서 순서대로 동작하게 구현하는 건 데코레이터로 구현하기 어렵다. +- 이러한 계층을 만들기 위한 초기 환경 코드들이 보기에 난잡해보일 수 있다. + +## 비슷한 패턴 + +- **어뎁터**패턴은 존재하는 객체에 대해서 인터페이스가 변경되지만, **데코레이터**는 인터페이스의 변경없이 객체를 확장시킨다. + 추가적으로 데코레이터는 재귀적 구성을 제공하고, 어뎁터는 그렇게 구성은 불가능하다. + +- **어뎁터**는 Wrapped 객체에 대해 다른 인터페이스를 제공하지만, **프록시**는 그것과 동일한 인터페이스를, **데코레이터**는 확장된 인터페이스를 제공한다. + +- **책임 연쇄 패턴**과 **데코레이터**는 클래스 구조상 비슷한 형식을 가지고 있다 . 두 패턴은 모두 한개의 객체로 실행시키기 위해 재귀적 합성방식을 이용한다. 그러나 몇몇 부분은 확연하게 차이점이 있다. + + - **책임 연쇄 핸들러**는 각각 서로가 독립적으로 임의의 조작을 실행시킬 수 있다. 또한 언제든지 이런 요청들을 멈출 수도 있다. + + - **데코레이터**들은 기본 인터페이스을 기반으로 일관성을 유지하면서 개체의 동작을 확장할 수 있다. 또한 데코레이터는 요청의 흐름을 끊을 수 없다. + +- 컴포짓과 데코레이터는 비슷한 구조 다이어 그램을 가지고 있다. 둘다 재귀적인 구성을 의존해서 무한정 객체를 구성할 수 있다. + + - 데코레이터는 컴포짓과 닮아있지만, 하나의 자식 컴포넌트만 가질 수 있다. + +- 컴포짓 그리고 데코레이터를 많이 사용하는 상황에서는 프로토 타입을 사용하는게 더 이점이 있다. 이런 패턴을 적용하게 되면, 복잡한 구조자체를 그냥 한번에 복제해서 구성하면 되기때문에 편리하다. + +- **데코레이터**는 객체에 스킨을 변경하는 것과 같고, **전략 패턴**은 내장을 바꾸는 역할을 한다. + +- **데코레이터**와 **프록시** 역시 비슷한 구조를 가지고 있지만, 매우 다른 의도를 가지고 있다. 두 패턴들은 모두 한 객체가 다른 객체에 작업의 일부를 위임하도록 되어 있는 구성으로 기반으로 하고 있다. 프록시는 서비스 객체의 수명 주기를 스스로 관리하지만, 데코레이터는 이런 생명주기를 client에 의해 제어한다.