Language/Design Pattern
[Design Pattern] 구조 패턴 - Decorator
JB1104
2023. 10. 27. 10:05
728x90
반응형
SMALL
Decorator 패턴
- 기존 코드를 변경하지 않고 부가기능을 추가하는 패턴
- 객체의 결합을 통해 기능을 동적으로 유연하게 확장할 수 있게 해주는 패턴
- 동적으로 유연하다는 것은 '런타임 시에 부가기능을 추가'가 가능하다는 것
- 결합이란 상속이 아닌 위임 (혹은 합성) 즉, Has-a 관계를 가지는 패턴임을 의미
- Decorator 객체를 조합함으로써 추가 기능의 조합을 설계하는 패턴
- Composite 패턴처럼 최상위에 Component 역할을 하는 인터페이스가 존재한다.
- 이 인터페이스는 ConcreateComponent와 Decorator가 둘 다 구현을 한 인터페이스다.
- Composite 패턴과 유사하지만 차이점은 Decorator가 여러 개의 Decorator를 가지고 있는 게 아니라 하나의
- Component 타입의 인스턴스를 가지고 있다.
- Decorator는 가지고 있는 하나의 Component를 호출하며, 호출 전, 후 부가적인 일들을 할 수 있다.

Decorator 패턴 적용 전
- 클라이언트는 CommentService의 addComment 기능을 통해 댓글 작성 가능
public class CommentService {
public void addComment(String comment) {
System.out.println(comment);
}
}
public class Client {
private CommentService commentService;
public Client(CommentService commentService) {
this.commentService = commentService;
}
public void writeComment (String comment) {
commentService.addComment(comment);
}
}
- 클라이언트에 CommentServcie를 주입하여 기능을 참조할 수 있도록 한다.
@Test
void test1() {
Client client = new Client(new CommentService());
client.writeComment("디자인패턴 공부");
client.writeComment("디자인패턴 어렵다");
client.writeComment("디자인패턴 그래도 공부해야지");
client.writeComment("디자인패턴 잘하고싶다면 >>>> http://커몬커몬.com");
}
이제 이 기본적인 기능에서 부가적인 기능을 추가해 보자.
상속을 통한 기능 추가
public class TrimmingCommentService extends CommentService {
@Override
public void addComment(String comment) {
super.addComment(trim(comment));
}
private String trim(String comment) {
return comment.replace("!", "~"); // ! -> ~ 변경
}
}
public class SpamFilteringCommentService extends CommentService {
@Override
public void addComment(String comment) {
if(!isNotSpam(comment)) { // http를 포함하면 댓글등록 X
super.addComment(comment);
}
}
private boolean isNotSpam(String comment) {
return comment.contains("http");
}
}
@Test
void test2() {
Client client = new Client(new CommentService());
client.writeComment("디자인패턴 공부!!!");
client.writeComment("디자인패턴 어렵다!!");
client.writeComment("디자인패턴 그래도 공부해야지!!!!!!");
client.writeComment("디자인패턴 잘하고싶다면 >>>> http://커몬커몬.com");
Client trimClient = new Client(new TrimmingCommentService());
trimClient.writeComment("디자인패턴 공부!!!");
trimClient.writeComment("디자인패턴 어렵다!!");
trimClient.writeComment("디자인패턴 그래도 공부해야지!!!!!!");
trimClient.writeComment("디자인패턴 잘하고싶다면 >>>> http://커몬커몬.com");
Client spamClient = new Client(new SpamFilteringCommentService());
spamClient.writeComment("디자인패턴 공부!!!");
spamClient.writeComment("디자인패턴 어렵다!!");
spamClient.writeComment("디자인패턴 그래도 공부해야지!!!!!!");
spamClient.writeComment("디자인패턴 잘하고싶다면 >>>> http://커몬커몬.com");
}
상속을 통한 기능의 확장 단점
- 가장 단순한 방법이지만, 만약 trim과 filter 기능을 동시에 사용하고 싶다면 새로운 클래스를 추가해서 추가적인 기능을 구현해야 한다. (번거롭고 클래스가 많이 증가한다.)
Decorator 패턴 사용
- Decorator 객체를 조합함으로써 추가 기능의 조합을 설계하는 패턴을 사용해 보자.
- 위임 (혹은 합성)을 이용한 기능 확장
- CommentService를 인터페이스로 만들고 기능과 구현을 분리한다.
- CommentServiceForDecorator - Component이고 DefaultCommentService - ConcreateComponent이다
public interface CommentServiceForDecorator {
void addComment(String comment);
}
public class DefaultCommentService implements CommentServiceForDecorator{
@Override
public void addComment(String comment) {
System.out.println(comment);
}
}
public class ClientForDecorator {
private CommentServiceForDecorator commentServiceForDecorator;
public ClientForDecorator(CommentServiceForDecorator commentServiceForDecorator) {
this.commentServiceForDecorator = commentServiceForDecorator;
}
public void writeComment (String comment) {
commentServiceForDecorator.addComment(comment);
}
}
1. Decorator 패턴의 핵심
- Decorator 역할을 하는 클래스가 딱 1개의 Component 만을 가지고 있으면서, 기능을 확장해 나가는 것 (Has-a 관계)
- Component를 감싸고 있는 CommentDecorator를 생성한다. 그리고 Decorator 역시 Component 타입이다.
- Decorator는 Component를 주입받고 기능만 호출해 준다.
public class CommentDecorator implements CommentServiceForDecorator{
private CommentServiceForDecorator commentServiceForDecorator;
public CommentDecorator(CommentServiceForDecorator commentServiceForDecorator) {
this.commentServiceForDecorator = commentServiceForDecorator;
}
@Override
public void addComment(String comment) {
commentServiceForDecorator.addComment(comment);
}
}
2. 기능의 확장
- 그 후에, 모든 기능의 확장은 Component 클래스를 감싸고 있는 Decorator (CommentDecorator)를 통한다.
- 여기서 중요한 것은, 확장된 기능을 Override 한 구현체에서 부모의 기능을 참조하여 기능을 확장한다는 것
- 그렇기 때문에 조합이 가능한 설계가 된다는 것이다.
public class TrimmingCommentDecorator extends CommentDecorator {
public TrimmingCommentDecorator(CommentServiceForDecorator commentServiceForDecorator) {
super(commentServiceForDecorator);
}
@Override
public void addComment(String comment) {
super.addComment(trim(comment));
}
public String trim(String comment) {
return comment.replace("!", "~");
}
}
public class SpamFilteringCommentDecorator extends CommentDecorator {
public SpamFilteringCommentDecorator(CommentServiceForDecorator commentServiceForDecorator) {
super(commentServiceForDecorator);
}
@Override
public void addComment(String comment) {
if(!isNotSpam(comment)) {
super.addComment(comment);
}
}
private boolean isNotSpam(String comment) {
return comment.contains("http");
}
}
- 결과적으로 기능이 결합되어 새로운 구현체를 만들지 않고도 trim 기능과 spam filter 기능을 조합할 수 있다.
@Test
void test3() {
CommentServiceForDecorator commentService = new DefaultCommentService();
if(spamYn) {
commentService = new SpamFilteringCommentDecorator(commentService);
}
if(trimYn) {
commentService = new TrimmingCommentDecorator(commentService);
}
ClientForDecorator client = new ClientForDecorator(commentService);
client.writeComment("디자인패턴 공부!!!!!!");
client.writeComment("디자인패턴 어렵다!!!!!!!");
client.writeComment("디자인패턴 그래도 공부해야지");
client.writeComment("디자인패턴 잘하고싶다면 >>>> http://커몬커몬.com");
}
실행시키는 순서
new TrimmingCommentDecorator(new SpamFilteringCommentDecorator(new DefaultCommentService()));
이 순서로 생성자가 실행되면서 해당 메소드를 차례차례 실행시키는 원리이다.
super로 최상위의 생성자나 메소드를 호출하고 있기에 결합하여 사용할 수 있다.
Decorator 패턴
장점
- 기존 코드를 변경하지 않고 Decorator로 감싸기만 해서 부가기능을 추가할 수 있다.
- 새로운 클래스를 만들지 않고 기존 기능을 조합할 수 있고 런타임에 동적으로 기능을 변경할 수 있다.
단점
- Decorator로 감싸면서 코드가 복잡해질 수 있는 단점이 있다.
728x90
반응형
LIST