-
데코레이터 패턴(Decorator Pattern)디자인 패턴 2022. 7. 19. 18:18
데코레이터 패턴(Decorator Pattern)
기존 코드를 변경하지 않고 부가기능을 추가하는 패턴
- 상속이 아닌 위임을 사용해서 보다 유연하게 (런타임에) 부가 기능을 추가하는 것도 가능하다.
데코레이터 패턴 적용 전
웹 사이트에서 사용자들이 작성한 Comment 를 관리하는 어플리케이션이 있다 가정해보자.
Comment 를 관리하는 CommentService 는 아래와 같으며 addComment 는 Comment 를 출력하는 메서드이다.
public class CommentService { public void addComment(String comment) { System.out.println(comment); } }
만약, "..." 과 특수기호는 관리하고 싶지 않다면 그에 맞는 기능을 추가해야한다.
해당 기능은 아래와같이 TrimmingCommentService 로 구현할 수 있다.
public class TrimmingCommentService extends CommentService { @Override public void addComment(String comment) { super.addComment(comment); } private String trim(String comment) { return comment.replace("...", ""); } }
이를 사용하는 Client 는 아래와 같이 구현할 수 있다.
public class Client { private CommentService commentService; public Client(CommentService commentService) { this.commentService = commentService; } private void writeComment(String comment) { commentService.addComment(comment); } public static void main(String[] args) { Client client = new Client(new TrimmingCommentService()); client.writeComment("오징어 게임"); client.writeComment("보는게 하는거보다 재밌을 수 가 없지..."); client.writeComment("https://hyokeun0419@tistory.com"); } } //결과 오징어 게임 보는게 하는거보다 재밌을 수 가 없지... https://hyokeun0419@tistory.com
새로운 기능 추가
여기서 특수기호 관리가 아닌 광고를 걸러내기 위한 새로운 서비스를 구현해야 한다면 SpamFilteringCommentService 를 하나 만들고 이전과 같이 CommentService 를 상속하여 만든다.
public class SpamFilteringCommentService extends CommentService { @Override public void addComment(String comment) { boolean isSpam = isSpam(comment); if(!isSpam) { super.addComment(comment); } } private boolean isSpam(String comment) { return comment.contains("http"); } }
이를 사용하기 위해서는 Client 는 아래와 같이 의존관계만 바꿔주면 된다.
public class Client { private CommentService commentService; public Client(CommentService commentService) { this.commentService = commentService; } private void writeComment(String comment) { commentService.addComment(comment); } public static void main(String[] args) { Client client = new Client(new SpamFilteringCommentService()); client.writeComment("오징어 게임"); client.writeComment("보는게 하는거보다 재밌을 수 가 없지..."); client.writeComment("https://hyokeun0419@tistory.com"); } } //결과 오징어 게임 보는게 하는거보다 재밌을 수 가 없지...
실행을 해보면 trim("...") 은 적용되지 않았지만, http 가 포함된 comment 는 걸러진것을 확인할 수 있다.
문제발생
- SpamService , TrimService 모두 사용하려면 이 두 코드가 포함된 새로운 CommentService 를 만들어야 한다.
- 아니면, 새로운 CommentService 를 만들어 이 둘을 이중 상속받아야 한다.
이를 데코레이터 패턴을 적용하여 해결해보자.
데코레이터 패턴 적용 후
먼저 공통된 기본 기능을 가진 CommentService 인터페이스
그리고 그 기본 기능을 구현하는 DefaultCommentService 는 아래와 같다.
public interface CommentService { void addComment(String comment); }
// Concrete Component public class DefaultCommentService implements CommentService { @Override public void addComment(String comment) { System.out.println(comment); } }
다음은 Decorator 에 속하는 CommentDecorator 를 구현해준다.
// wrapper, decorator public class CommentDecorator implements CommentService { private CommentService commentService; public CommentDecorator(CommentService commentService) { this.commentService = commentService; } @Override public void addComment(String comment) { System.out.println(comment); } }
CommentDecorator 는 CommentService 를 참조로 가지는 것을 볼 수 있다.이것을 wrappee 라고 하는데, 다양한 Decorator 를 참조하기 위해 반드시 필요하다.
ConcreteDecorator 들을 구현하자.
TrimmingCommentDecorator
public class TrimmingCommentDecorator extends CommentDecorator { public TrimmingCommentDecorator(CommentService commentService) { super(commentService); } @Override public void addComment(String comment) { super.addComment(trim(comment)); } private String trim(String comment) { return comment.replace("...", ""); } }
SpamFilteringCommentDecorator
public class SpamFilteringCommentDecorator extends CommentDecorator { public SpamFilteringCommentDecorator(CommentService commentService) { super(commentService); } @Override public void addComment(String comment) { if(isNotSpam(comment)) { super.addComment(comment); } } private boolean isNotSpam(String comment) { return !comment.contains("http"); } }
DateCommentDecorator
import java.time.LocalDateTime; public class DateCommentDecorator extends CommentDecorator{ public DateCommentDecorator(CommentService commentService) { super(commentService); } @Override public void addComment(String comment) { super.addComment(addDate(comment)); } private String addDate(String comment) { return comment + " [" + LocalDateTime.now()+"]"; } }
Client
public class Client { //Component 사용 private CommentService commentService; public Client(CommentService commentService) { this.commentService = commentService; } public void writeComment(String comment) { commentService.addComment(comment); } }
App
public class App { private static boolean enableSpamFilter = true; private static boolean enableTrimming = true; private static boolean enableDate = true; public static void main(String[] args) { CommentService commentService = new DefaultCommentService(); //런타임시에 추가된다. if (enableSpamFilter) { commentService = new SpamFilteringCommentDecorator(commentService); } if (enableTrimming) { commentService = new TrimmingCommentDecorator(commentService); } if (enableDate) { commentService = new DateCommentDecorator(commentService); } Client client = new Client(commentService); client.writeComment("오징어 게임"); client.writeComment("보는게 하는거보다 재밌을 수 가 없지..."); client.writeComment("https://hyokeun0419@tistory.com"); } } //결과 오징어 게임 [2022-07-19T18:12:51.824201] 보는게 하는거보다 재밌을 수 가 없지... [2022-07-19T18:12:51.839300] https://hyokeun0419@tistory.com [2022-07-19T18:12:51.839345]
결과값과 아래 이미지와 같이 객체의 결합을 통해 모든 Decorator 가 적용된다는 것을 알 수 있다.
장점과 단점
장점
- 새로운 클래스를 만들지 않고 기존 기능을 조합할 수 있다.
- 컴파일 타임이 아닌 런타임에 동적으로 기능을 변경할 수 있다.
- 현재 Client가 인터페이스를 사용함으로써 DI원칙을 적용할 수 있다.
단점
데코레이터를 조합하는 코드가 복잡할 수 있다.
참고
코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의
디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할
www.inflearn.com
'디자인 패턴' 카테고리의 다른 글
플라이웨이트 패턴 (Flyweight Pattern) (0) 2022.07.22 퍼사드 패턴 (Facade Pattern) (0) 2022.07.19 컴포짓 패턴 (Composite Pattern) (0) 2022.07.12 브릿지 패턴 (Bridge Pattern) (0) 2022.07.12 어댑터 패턴 (Adapter Pattern) (0) 2022.07.05