Skip to content

데코레이터 패턴은 주어진 상황 및 용도에 따라 객체에 책임을 덧붙이는 패턴이다. 기본 기능에 추가할 수 있는 기능의 종류가 많은 경우에 각 추가 기능을 Decorator 클래스로 정의 한 후 필요한 Decorator 객체를 합함으로써 추가 기능의 조합을 설계 하는 방식이다. 데코레이터 패턴은 객체의 결합 을 통해 기능을 동적으로 유연하게 확장 할 수 있게 해준다.


예시 코드

public interface CommentService {
void addComment(String comment);
}
public class DefaultCommentService implements CommentService{
@Override
public void addComment(String comment) {
System.out.println(comment);
}
}

댓글을 추가하는 서비스 인터페이스와 그걸 상속받아 구현한 클래스가 있다. 여기에 댓글을 필터링하는 로직을 데코레이터 패턴을 통해 추가해볼 것이다.


@RequiredArgsConstructor
public class CommentDecorator implements CommentService{
private final CommentService commentService;
@Override
public void addComment(String comment) {
commentService.addComment(comment);
}
}

CommentService를 상속받아 CommentDecorator클래스를 만든다. CommentService를 주입받아서 CommentService의 addComment() 메서드를 호출한다. 이 친구가 이제부터 추가할 Decorator들의 근간이 되어줄 것이다.


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");
}
}
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() +"]";
}
}
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("...", "");
}
}

addComment() 메서드에 필터링, 날짜 추가 등 기능을 추가(Decorate) 해주는 Decorator들이다. 이제 이걸 DefaultCommentService에 추가해주면 된다.


public class App {
private static final boolean enabledSpamFilter = true;
private static final boolean enabledTrimming = true;
private static final boolean enabledDate = true;
public static void main(String[] args) {
CommentService commentService = new DefaultCommentService();
if (enabledSpamFilter) commentService = new SpamFilteringCommentDecorator(commentService);
if (enabledTrimming) commentService = new TrimmingCommentDecorator(commentService);
if (enabledDate) commentService = new DateCommentDecorator(commentService);
commentService.addComment("댓글");
commentService.addComment("ㅏㅓㅑㅐ...............");
commentService.addComment("https");
}
}

바로 이렇게 추가해준다. 이렇게 하면 조건에 따라 동적으로 Decorator 로직을 추가해줄 수 있다. Decorator를 모두 추가한 commentService에서 addComment()를 호출하면 Decorator의 addComment()가 차례대로 실행된 후에 서비스가 실행된다. 근데 이게 왜 될까? 같은 인터페이스를 상속받았으니까 특정 Decorator를 commentService에 대입할 수 있다는건 알겠는데, 그게 어떻게 이렇게 차곡차곡 쌓여서 야무지게 실행될 수 있지? 라는 생각이 들었다. 각 클래스의 addComment() 메서드가 호출될 때 각 메서드명을 출력하여 메서드의 실행 시기를 추척해보니 다음과 같은 결과가 나왔다.

DateCommentDecorator.addComment
CommentDecorator.addComment

TrimmingCommentDecorator.addComment
CommentDecorator.addComment

SpamFilteringCommentDecorator.addComment
CommentDecorator.addComment

DefaultCommentService.addComment

댓글[2022-08-11T14:45:54.203837300]


Decorator의 addComment()가 main에서 추가했던 순서의 역순으로 실행되는 것을 볼 수 있다. Decorator를 추가할 때 commentService를 필드로 가지도록 생성하여 대입했던 것을 생각하면 쉽게 구조를 파악할 수있다.


DateCommentDecorator(CommentService)의 addComment()가
TrimmingCommentDecorator(CommentService)를 호출하고 TrimmingCommentDecorator가
SpamFilteringCommentDecorator(CommentService)를 호출하고 SpamFilteringCommentDecorator가
DefaultCommentService(CommentService)를 호출하는 구조였다.

마치 마트료시카처럼 관계가 중첩되어 있어서 가장 겉에 있는 Decorator가 안에 있는 Decorator를 호출하다가, 마지막에 진짜 DefaultCommentService가 호출되면서 출력되었다. 이름은 Decorator지만 Decorator 클래스가 Service클래스를 꾸며주는게 아니라 감싸는 구조였던 것이다...! 마치 네트워크에서 정보를 캡슐화하여 정보를 전송하는 것과 비슷한 느낌이다.

아무튼 이 데코레이터 패턴을 사용하면 동적으로 기능을 자유롭게 추가할 수 있다는 장점이 있다. 특정 기능을 유연하게 추가해야 할 때 이 패턴을 활용하면 좋을 것 같다.

링크로 가면 코드를 볼 수 있다.