데코레이터(Decorator) 패턴
디자인 패턴 ·데코레이터 패턴은 객체에 동적으로 기능을 추가할 수 있는 디자인 패턴입니다.
이 패턴은 상속을 통해 기능을 확장하는 대신, 객체를 감싸는 데코레이터 클래스를 사용하여 기능을 추가하고 조합합니다.
이를 통해 코드의 유연성과 확장성을 향상시킬 수 있습니다.
구성 요소
컴포넌트(Component): 기본 기능을 정의하는 인터페이스 또는 추상 클래스입니다.
데코레이터(Decorator): 컴포넌트를 감싸는 역할을 하며, 컴포넌트와 동일한 인터페이스를 구현합니다.
구체적인 데코레이터(Concrete Decorator): 데코레이터를 상속받아 새로운 기능을 추가하거나 기존 기능을 수정합니다.
구현:
- 컴포넌트 인터페이스를 정의합니다. 이 인터페이스는 기본 기능을 정의합니다.
- 기본 기능을 구현하는 구체적인 컴포넌트 클래스를 생성합니다.
- 데코레이터 클래스를 생성하여 컴포넌트를 감싸고, 추가 기능을 정의합니다. 이 클래스는 컴포넌트와 동일한 인터페이스를 구현합니다.
- 구체적인 데코레이터 클래스를 생성하여 데코레이터를 상속받아 새로운 기능을 추가하거나 기존 기능을 수정합니다.
- 클라이언트에서는 원하는 기능의 조합을 위해 데코레이터를 사용하여 컴포넌트를 감싸고, 기능을 호출합니다.
장점
객체에 동적으로 기능을 추가할 수 있습니다.
상속을 통해 기능을 확장하는 대신, 객체를 감싸는 데코레이터 클래스를 사용하여 기능을 추가하고 조합할 수 있습니다.
기존 코드의 수정 없이 기능을 추가하거나 수정할 수 있습니다.
단점
객체가 많이 중첩될 경우 데코레이터 클래스의 수가 증가하여 코드 복잡성이 증가할 수 있습니다.
예시
Comment를 추가하는 서비스가 있고, 옵션에 따라 url 차단 기능과 trim 기능을 선택적으로 제공하려고한다.
Component
public interface CommentService {
void addComment(String comment);
List<String> getCommentList();
}
Default Component
public class DefaultCommentService implements CommentService {
private final List<String> commentList = new ArrayList<>();
@Override
public void addComment(String comment) {
commentList.add(comment);
}
@Override
public List<String> getCommentList() {
return commentList;
}
}
Decorator
public abstract class CommentServiceDecorator implements CommentService{
protected final CommentService commentService;
public CommentServiceDecorator(CommentService commentService) {
this.commentService = commentService;
}
@Override
public final List<String> getCommentList() {
return commentService.getCommentList();
}
}
Concrete Decorator
public class SpamFilteringCommentServiceDecorator extends CommentServiceDecorator {
public SpamFilteringCommentServiceDecorator(CommentService commentService) {
super(commentService);
}
@Override
public void addComment(String comment) {
boolean isSpam = isSpam(comment);
if (!isSpam) {
this.commentService.addComment(comment);
}
}
private boolean isSpam(String comment) {
return comment.contains("http");
}
}
public class TrimmingCommentServiceDecorator extends CommentServiceDecorator {
public TrimmingCommentServiceDecorator(CommentService commentService) {
super(commentService);
}
@Override
public void addComment(String comment) {
this.commentService.addComment(trim(comment));
}
private String trim(String comment) {
return comment.replace("...", "");
}
}
Client
public class CommentServiceTest {
// 파라미터 개수만큼 테스트를 진행
@ParameterizedTest
// isSpamFilteringWithIsTrimming 리턴값을 파라미터로 사용하도록 지정
@MethodSource("isSpamFilteringWithIsTrimming")
public void comment_test(boolean isEnabledSpamFiltering, boolean isEnabledTrimming) {
CommentService commentService = new DefaultCommentService();
// isEnabledSpamFiltering이 true면 url 코멘트 삭제
if(isEnabledSpamFiltering){
commentService = new SpamFilteringCommentServiceDecorator(commentService);
}
// isEnabledTrimming이 true면 comment trim
if(isEnabledTrimming){
commentService = new TrimmingCommentServiceDecorator(commentService);
}
List<String> commentList = getCommentList(commentService);
commentList.stream().forEach(System.out::println);
assertTrue(commentList.contains("오징어게임"));
if(isEnabledTrimming){
assertTrue(commentList.contains("보는게 하는거 보다 재밌을 수가 없지"));
}else{
assertTrue(commentList.contains("보는게 하는거 보다 재밌을 수가 없지..."));
}
if(!isEnabledSpamFiltering){
assertTrue(commentList.contains("http://kktrkkt.github.io"));
}
}
private static Stream<Arguments> isSpamFilteringWithIsTrimming() {
return Stream.of(
Arguments.of(true, true),
Arguments.of(true, false),
Arguments.of(false, true),
Arguments.of(false, false)
);
}
private static List<String> getCommentList(CommentService commentService) {
commentService.addComment("오징어게임");
commentService.addComment("보는게 하는거 보다 재밌을 수가 없지...");
commentService.addComment("http://kktrkkt.github.io");
return commentService.getCommentList();
}
}
결론
데코레이터 패턴은 객체에 동적으로 기능을 추가할 수 있는 유연한 디자인 패턴입니다.
상속을 통해 기능을 확장하는 대신, 객체를 감싸는 데코레이터 클래스를 사용하여 기능을 추가하고 조합합니다.
이를 통해 코드의 유연성과 확장성을 향상시킬 수 있습니다. 단, 객체가 많이 중첩될 경우 데코레이터 클래스의 수가 증가하여 코드 복잡성이 증가할 수 있습니다.
예제는 이곳 에서 확인하실수 있습니다.