본문 바로가기

디자인패턴

데코레이터 패턴

데코레이터 패턴이란?

스크린샷 2024-05-07 오전 10 25 10

기존에 있는 코드를 변경하지 않으면서 부가적인 기능을 추가할 수 있는 패턴이다.

데코레이터 패턴 적용 전

예시로 댓글 서비스를 확인해보자

public class CommentService {

    public void addComment(String comment) {
        System.out.println(comment);
    }
}

여기서 댓글의 특정 키워드를 trim 하는 기능을 추가해보자 .

public class TrimmingCommentService extends CommentService {

    @Override
    public void addComment(String comment) {
        System.out.println(trim(comment));
    }

    private String trim(String comment) {

        return comment.replace("...", "");
    }
}
public static void main(String[] args) {

    Client client = new Client(new TrimmingCommentService());
    client.writeComment("오징어게임");
    client.writeComment("보는게 하는거 보다 재밌을 수가 없지...");
    client.writeComment("<http://birdiehyun.me>");
}

여기서 spam 기능까지 차단하고 싶다면, 다시 commentService를 상속받은 클래스를 만들 수 있을 것이다.

public class SpamFilteringCommentService extends CommentService {

        @Override
        public void addComment(String comment) {
            if (comment.contains("http")) {
                return;
            }
            super.addComment(comment);
        }
}
public static void main(String[] args) {

    Client client = new Client(new SpamFilteringCommentService());
    client.writeComment("오징어게임");
    client.writeComment("보는게 하는거 보다 재밌을 수가 없지...");
    client.writeComment("<http://birdiehyun.me>");
}

그러나 위의 방법으로 구현한다면 TrimmingCommentService는 적용되지 않게 된다.

trimming 과 spamfilter를 함께 적용하고 싶다면, TrimmingAndSpamFilterCommnetService를 새로 만들어야 할 것이고, 부가 기능이 더 추가된다면 더욱 많은 클래스들이 필요해지게 될것이다.

데코레이터 패턴 적용

가장 먼저 동작의 기본이 되는 Component를 정의해줍니다.

public interface CommentService {

    void addComment(String comment);
}

그리고 그 동작의 기본적인 구현체인 Concrete Component를 구현합니다.

public class DefaultCommentService implements CommentService {

    @Override
    public void addComment(String comment) {
        System.out.println(comment);
    }
}

Concrete Component에 Decorator를 붙이기 위한 Decorator 클래스를 구현합니다.

public class CommentDecorator implements CommentService {

    private CommentService commentService;

    public CommentDecorator(CommentService commentService) {
        this.commentService = commentService;
    }

    @Override
    public void addComment(String comment) {
        commentService.addComment(comment);
    }
}

이때 중요한 점은 CommentService를 implements하고, 필드로 CommentService를 가지고 있다는 것입니다.

이제 CommentService를 꾸며줄 Decorators를 만들어줍니다.

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

이제 CommentService를 사용하는 클라이언트 코드와, 실제 애플리케이션을 살펴보겠습니다.

public class Client {

    private CommentService commentService;

    public Client(CommentService commentService) {
        this.commentService = commentService;
    }

    public void writeComment(String comment) {
        commentService.addComment(comment);
    }
}
public class Application {

    private static boolean enabledSpamFiltering = true;
    private static boolean enabledTrimming = true;

    public static void main(String[] args) {
        CommentService commentService = new DefaultCommentService();

        if (enabledSpamFiltering) {
            commentService = new SpamFilteringCommentDecorator(commentService);
        }

        if (enabledTrimming) {
            commentService = new TrimmingCommentDecorator(commentService);
        }

        Client client = new Client(commentService);
        client.writeComment("오징어게임");
        client.writeComment("보는게 하는거 보다 재밌을 수가 없지...");
        client.writeComment("<http://birdiehyun.me>");
    }
}

상속을 사용했다면 기능에 맞는 클래스를 계속해서 상속하고 구현해주어야 했지만, 데코레이터 패턴을 적용한다면 각각의 클래스들은 자신만의 역할을 수행하고, 확장에 더욱 유연한 코드가 될 수 있다.

Spam Filter 역할은 Spam Filter가 하고, Trim 역할은 Trim이 하는 단일 책임의 원칙,

확장에는 열려있지만, 변경에는 닫혀있는 OCP 원칙,

구현체가 아닌, Comment Service라는 추상체에 의존하는 의존관계 역전 원칙을 잘 지킨 패턴이라고 볼 수 있다.

'디자인패턴' 카테고리의 다른 글

레거시 코드 전략패턴 적용기  (0) 2024.03.15