디자인 패턴

데코레이터 패턴(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