ABOUT ME

-

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

     

Designed by Tistory.