데코레이터 패턴이란?
기존에 있는 코드를 변경하지 않으면서 부가적인 기능을 추가할 수 있는 패턴이다.
데코레이터 패턴 적용 전
예시로 댓글 서비스를 확인해보자
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 |
---|