데코레이터 패턴

모지리 개발자·2022년 11월 9일
0

design pattern

목록 보기
1/1

Intro

최근 zum 인터넷의 기술블로그에서 OOP 기반 선착순 투표 시스템 아키텍처의 글을 읽으면서 데코레이터 패턴에 대한 내용이 있었습니다. 기존에 데코레이터 패턴에 대해서 기존 기능에 부가적인 기능을 더해서 사용해야 할 때 사용하는 패턴..? 정도의 애매하게 알고있었는데 이글을 읽으면서 아키텍처를 설계할 때 상당히 유용하게 쓰일 수 있는 패턴이라는 것을 알게되었고 데코레이터 패턴에 대해 공부하고 정리하면서 작성한 글입니다.

Decorator 패턴이란?

우선 zum인터넷 기술블로그의 해당 포스트에서는 다음과 같이 작성하였습니다.

"Decorator 패턴을 선택한 이유는 선착순 투표든, 주주투표든, 관심 종목 투표든 기본적인 투표 시스템이 갖춘 기능들(예약/단일/복수/기간) 공통적으로 사용하면서 추가적인 기능을 사용하기 위함입니다."

Decorator패턴은 기존에 있는 코드를 변경하지 않으면서 부가적인 기능을 추가할 수 있는 매우 유용한 패턴입니다.

장점은 런타임에 다이나믹하게 부가기능을 추가할 수 있다는 것이 장점입니다.

데코레이터 패턴의 기본 구조는 아래와 같습니다.

코드로 살펴보기

코드는 코딩으로 학습하는 GoF의 디자인 패턴를 참고하여 작성하였습니다.

Decorator 패턴 미적용시

CommentService.java

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

Client.java

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("항상 부족하다 생각하고 발전하겠습니다ㅎㅎㅎ");
    }

}

위의 코드와 같이 단순히 문자열을 출력하는 기능이 있다고 가정해보겠습니다.

만약 문자열을 출력을 하는데 trim()을 해주는 기능을 추가해야한다고 합니다.
이럴 때 간단하게 생각해볼 수 있는 것은 상속을 활용한는 것 입니다.

TrimmingCommentService.java

public class TrimmingCommentService extends CommentService {

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

    private String trim(String comment) {
        return comment.replace("!!!", "");
    }
	// 그냥 이런 trim()기능이 있다고 생각해봅시다ㅎㅎ
}

기존 위에서 작성했었던 CommentService를 상속받아 메서드를 override해줌으로써 Trim()기능을 추가할 수 있습니다.

상속을 활용하여 부가적인 기능을 추가하는 것은 쉽지가 않다.

상속을 이용해서 기존의 코드를 변경하지 않고도 부가적인 기능을 추가할 수 있었습니다. 하지만 문제는 다음과 같습니다.

  • 컴파일 타임에 고정적인 코드가 정해진다.
  • 상속을 쓰면 굉장히 유연하지는 않다.

유연하지 않은 이유는 아래와 같습니다.

위에선 작성한 "!!!"를 제거하는 기능 이외에도 스팸문자열은 출력하지 않는 기능을 추가해야한다고 가정해보겠습니다. 이 글에서 스팸 문자열은 "ㅎㅎㅎ"이 들어가 있는 문자열이 스팸문자열이라고 가정해보겠습니다.

SpamFilteringCommentService.java

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

스팸문자열을 출력하지않는 기능을 사용하기 위해서 Client코드는 아래와 같이 작성해서 사용하여야 할 것 입니다.
Client client = new Client(new SpamFilteringCommentService());

근데 생각해보니 이렇게 코드를 구성하면 위에서 작성했던 "!!!"를 제거해주는 기능을 사용하지 못합니다.

그러면 두개의 기능을 모두 구현 가능한 클래스를 또 만들어야하나? 라는 생각이 듭니다. 이를 통해 상속(Java는 다중상속을 허용하지 않음)은 유연하지 않다고 설명하였습니다.

위의 예시를 통해 상속을 통해서만 부가적인 기능을 확장해 나가는 것은 상당히 불편한 일임을 알 수 있었습니다.

Decorator패턴 적용해보기

위의 코드를 바탕으로 Decorator패턴을 적용해보도록하겠습니다.
이해를 쉽게 하기 위해 위에서 보여드린 Decorator패턴의 구조를 바탕으로 클래스의 구조를 보여드리겠습니다.
하고자 하는 것은 아래와 같습니다.

Client.java

public class Client {

    private CommentService commentService;

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

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

CommentService.java

public interface CommentService {
    void addComment(String comment);
}

DefaultCommentService.java

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

CommentDecorator.java

public class CommentDecorator implements CommentService {

    private CommentService commentService;

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

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

TrimmingCommentDecorator.java

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.java

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

Decorator패턴을 적용시켜서 코드를 작성해보았습니다.

실행해보기

App.java

public class App {

    private static boolean enabledSpamFilter = true;

    private static boolean enabledTrimming = true;

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

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

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

        Client client = new Client(commentService);
        client.writeComment("모지리 개발자");
        client.writeComment("박창환 입니다!!!");
        client.writeComment("항상 부족하다 생각하고 발전하겠습니다ㅎㅎㅎ");
    }
}

위와 같이 코드를 작성해서 테스트해보면 동적으로 기능을 부가적으로 추가할 수 있는 것을 알 수 있습니다. 상속을 사용했다면 A기능 클래스, B기능 클래스, A-B기능 클래스를 경우의 수에 맞추어 계속 만들어나가야했지만 데코레이터 패턴을 이용하여 문제를 할 수 있습니다.

데코레이터 패턴 장단점

장점

  • 새로운 클래스를 만들지 않고 기존 기능을 조합할 수 있습니다.
  • 컴파일 타임이 아닌 런타임에 동적으로 기능을 변경할 수 있습니다.

단점

  • 데코레이터를 조합하는 코드가 복잡해질 수 있습니다.

다시 블로그 글 읽어보기

데코레이터 패턴을 공부한 후 다시 OOP 기반 선착순 투표 시스템 아키텍처의 글을 읽어보았습니다. 이제야 왜 데코레이터 패턴을 사용하여 투표기능을 구현했는지 알 수 있었습니다.

결론

이 글은 데코레이터 패턴을 설명한 글이기도 했지만 아는만큼 보이고 아는만큼 읽힌다는 것을 느끼면서 작성한 글이기도 합니다. 아직까지는 세상 모든 사람들이 내 글을 읽고 내생각에 공감하고, 내생각에 놀랄 수 있는 글을 작성하기는 어렵지만 언젠간 저도 제 글을 읽고 사람들이 많은 인사이트를 얻고 도움을 얻을 수 있었으면 좋겠습니다. 감사합니다.

제가 잘못이해하고 있거나 잘못 작성한 부분이 있다면 지적, 비판, 피드백 뭐든 해주시면 감사하겠습니다!

profile
항상 부족하다 생각하며 발전하겠습니다.

0개의 댓글