(인프런) 코딩으로 학습하는 GoF의 디자인 패턴 - 백기선, 강의를 보고 정리한 글입니다.
코드는 GitHub 에 있습니다
#1. 객체 생성 관련 패턴 |
#2. 구조 관련 패턴 |
#3. 행동 관련 패턴 |
||
✔️ 데코레이터 패턴
' 데코레이터 패턴은 기존코드를 변경하지 않고 부가기능을 추가하는 패턴이다. ' 라고 정의됩니다.
- 데코레이터 패턴이란, 객체의 결합 을 통해 기능을 동적으로 유연하게 확장 할 수 있게 해주는 패턴입니다.
- 동적으로 유연하게 확장할 수 있다는건→ 런타임 시에 부가기능을 추가하는 것도 가능하다는 이야기 입니다.
- 객체의 결합이란 상속이 아닌, 위임(Delegation 혹은 합성이라고도 하는것 같음) 즉 Has-a 관계를 가지는 패턴임을 의미합니다.
- 데코레이터 패턴은 Decorator 객체를 조합함으로써 추가 기능의 조합을 설계하는 패턴입니다.
→ 데코레이터 패턴의 구조를 살펴보면, Decorator가 기존의 기능을 정의한 Component 클래스를 감싸고 있습니다. 즉, 멤버변수로써 가지고있으면서, Component 가 가지는 기능(Operation)을 정의하고 있습니다.
→ 그럼 이제 기능의 확장은 Component 클래스를 직접 바라보지 않고 Decorator 를 상속받아 확장해 나갈 수 있습니다.
→ 풀어설명하자면, Component 의 기능을 구현한 Decorator 는 부모 인터페이스를 멤버변수로 가지고 있기에 외부에서 의존성을 주입받을 수 있는 상태가 되고
이 Decorator 의 기능을 확장한 자식 클래스(기능을 확장한 Decorator) 는 부모의 기능을 참조하여 사용하는 구조를 이루게되면,
기능을 확장한 Decorator 를 외부에서 주입받아 확장되는 형태가 가능해집니다!
말로는 복잡하니.. 코드로 보시져!
✔️ 데코레이터 패턴 예시
먼저 데코레이터 패턴을 적용하지 않고 상속으로 기능을 확장했을 때와 → 이걸 데코레이터 패턴으로 기능을 확장해나가는 예시로 변경해보겠슴당!
- 예시는 간단하게, client 가 댓글을 작성하고, 작성한 댓글을 출력하는 예시입니다.
- 댓글 출력 기능을 단순 출력 → 특정 문자 변경 (trim) → 특정 문자 탐지 (SpamFiltering) 으로 기능을 확장해나갈 시나리오입니다.
↓
1. 상속을 통한 기능의 확장 예시
Client.class 와 CommentService
- 단순하게 Client는 CommentService 의 addComment 기능을 통해 댓글을 작성할 수 있습니다.
public class Client {
private CommentService commentService;
public Client(CommentService commentService) {
this.commentService = commentService;
}
public void writeComment(String comment) {
commentService.addComment(comment);
}
}
public class CommentService {
public void addComment(String comment) {
System.out.println(comment);
}
}
App.Class
- 외부에서 Client 에 CommentService 를 주입하여 기능을 참조할 수 있도록 해줍니다.
- 단순하게 댓글을 작성하면 그대로 출력함을 확인할 수 있습니다.
public class App {
public static void main(String[] args) {
Client client = new Client(new CommentService());
client.writeComment("오징어 게입 수위 미쳤다ㄷㄷ → http://광고광고.com");
client.writeComment("내가 왕이 될 상인가");
client.writeComment("내가임마!!! 어이!!");
}
}
↓
👏🏻 이제 기능을 확장해 봅니다. 가장 간단한 방법은 "상속" 을 통해 기능을 확장해 나가는 것입니다.
1) tirm 기능의 확장
TrimmingCommentService.class
/**
* 상속으로 문제를 풀려고하니, 모든 경우의 수에 맞는 하위 클래스를 만들어주야하는 문제점이 있음
*/
public class TrimmingCommentService extends CommentService{
@Override
public void addComment(String comment) {
super.addComment(trim(comment));
}
private String trim(String comment) {
return comment.replace("!", "~");
}
}
2) Spam 필터링 기능 확장
SpamFilteringCommentService.class
public class SpamFilteringCommentService extends CommentService{
@Override
public void addComment(String comment) {
if (isNotSpam(comment)) {
super.addComment(comment);
}
}
private boolean isNotSpam(String comment) {
return !comment.contains("http");
}
}
↓
이제 외부에서 호출할때, 상속받아 기능을 확장한 구현체를 상황에 맞게 주입해주면 기능을 확장에서 사용할 수 있습니다.
public class App {
public static void main(String[] args) {
Client client = new Client(new CommentService());
client.writeComment("오징어 게입 수위 미쳤다ㄷㄷ → http://광고광고.com");
client.writeComment("내가 왕이 될 상인가");
client.writeComment("내가임마!!! 어이!!");
System.out.println("--------- ");
Client trimClient = new Client(new TrimmingCommentService());
trimClient.writeComment("오징어 게입 수위 미쳤다ㄷㄷ → http://광고광고.com");
trimClient.writeComment("내가 왕이 될 상인가");
trimClient.writeComment("내가임마!!! 어이!!");
System.out.println("--------- ");
Client spamFilterClient = new Client(new SpamFilteringCommentService());
spamFilterClient.writeComment("오징어 게입 수위 미쳤다ㄷㄷ → http://광고광고.com");
spamFilterClient.writeComment("내가 왕이 될 상인가");
spamFilterClient.writeComment("내가임마!!! 어이!!");
}
}
📌 상속을 통한 기능의 확장 단점
- 가장 단순한 방법이지만, 만약 trim 기능과 spam 기능을 동시에 하고싶다면, TrimSpamFilteringComment~~ 와 같은 추가적인 구현 클래스를 다시 만들어야하는 단점이 존재합니다.
- 이러한 단점을 커버하기위해 Decorator 객체를 조합함으로써 추가 기능의 조합을 설계하는 데코레이터 패턴을 사용할 수 있습니다.
2. 위임을 이용한 데코레이터 패턴 설계를 통한 기능 확장 예시
Client.class 와 CommentService
- 예제는 똑같이 client가 commentService 의 기능을 이용해 댓글을 작성합니다.
- 하지만, CommentService를 인터페이스를 기능을 분리합니다. (ISP)
- 그리고, 인터페이스의 가장 기본적인 기능을정의한 구현체 DefaultCommentService를 구현해줍니다.
public class Client {
private CommentService commentService;
public Client(CommentService commentService) {
this.commentService = commentService;
}
public void writeComment(String comment) {
commentService.addComment(comment);
}
}
public interface CommentService {
void addComment(String comment);
}
public class DefaultCommentService implements CommentService{
@Override
public void addComment(String comment) {
System.out.println(comment);
}
}
이렇게한다면 일단, 외부에서 클라이언트는 위와 동일하게 단순한 댓글 작성 기능을 사용할 수 있습니다.
public class App {
public static void main(String[] args) {
CommentService commentService = new DefaultCommentService();
Client client = new Client(commentService);
client.writeComment("오징어 게입 수위 미쳤다ㄷㄷ → http://광고광고.com");
client.writeComment("내가 왕이 될 상인가");
client.writeComment("내가임마!!! 어이!!");
}
}
그럼 이제 데코레이터 패턴을 적용하여 기능을 확장해 봅시다!
↓
1. 데코레이터 패턴의 핵심
- Decorator 의 역할을 하는 클래스가 딱 1개의 Commponent 만을 가지고 있으면서, 기능을 확장해 나가는 것입니다. (has-a 관계)
public class CommentDecorator implements CommentService {
private CommentService commentService;
public CommentDecorator(CommentService commentService) {
this.commentService = commentService;
}
@Override
public void addComment(String comment) {
commentService.addComment(comment);
}
}
2. 기능의 확장
- 그리고 난 후, 모든 기능의 확장은 Component 클래스를 감싸고있는 Decorator를 통해 이루어집니다.
1) trim 기능을 확장한 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("!", "~");
}
}
2) SpamFilter 기능을 확장한 SpamFilteringDecorator
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");
}
}
👏🏻 여기서 볼것은, 확장된 기능을 Overriding 한 각각 구현체에서 부모의 기능을 참조하여 기능을 확장한다는 것입니다.
그렇기 때문에 조합이 가능한 설계가 된 것이죠
3. APP
- 데코레이터 패턴은 런타임 시에 기능을 확장할 수 있다고 했습니다.
- 아래처럼 단순하게 조건문을 걸어 런타임 시 기능을 확장할 수 도 있습니다.
- 지금 구조를 보시면
- Client 에 주입되는 CommentService 의 구현체는 → TrimmingCommentDecorator
- TrimmingCommentDecorator 에 주입되는 CommentService 의 구현체는 → SpamFilteringCommnetDecorator
- SpamFilteringCommnetDecorator 에 주입되는 CommentService 의 구현체는 → DefaultCommentService
- 로, CommentService 인터페이스를 감싸고 있는 CommentDecorator의 구현체를 통해 기능을 이어나가고 있습니다. (DefaultCommentService 제외)
/**
* 데코레이터패턴을 사용하여 상속이 아닌 위임으로 문제를 해결
*/
public class App {
private static boolean enabledSpamFilter = true;
private static boolean enableTrimming = true;
public static void main(String[] args) {
CommentService commentService = new DefaultCommentService();
if (enabledSpamFilter) {
commentService = new SpamFilteringCommentDecorator(commentService);
}
if (enableTrimming) {
commentService = new TrimmingCommentDecorator(commentService);
}
Client client = new Client(commentService);
client.writeComment("오징어 게입 수위 미쳤다ㄷㄷ → http://광고광고.com");
client.writeComment("내가 왕이 될 상인가");
client.writeComment("내가임마!!! 어이!!");
}
}
이렇게 함으로써, 기능이 결합되어 새로운 구현체를 만들지 않고도 trim 기능과 spam filter 기능을 조합해 사용할 수 있는 구조가 된것입니다!
이상으로 데코레이터 패턴에 대해 공부해보았습니다
디자인 패턴을 공부할 수록 객체의 상속보다는 위임(or 합성이란 단어)을 많이 사용하는데
이에 대해 친숙해질 필요가 있다고 느껴지네요,,
그럼 끝!
'Java > Design Pattern' 카테고리의 다른 글
[디자인 패턴] 구조 패턴 - 플라이웨이트 패턴 (Flyweight Pattern) (0) | 2023.03.15 |
---|---|
[디자인 패턴] 구조 패턴 - 퍼사드 패턴 (Facade Pattern) (0) | 2023.03.09 |
[디자인 패턴] 구조 패턴 - 컴포지트 패턴 (Composite Patterns) (0) | 2023.03.01 |
[디자인 패턴] 구조 패턴 - 브릿지 패턴 (Bridge Patterns) (2) | 2023.02.24 |
[디자인 패턴] 구조 패턴 - 어댑터 패턴 (Adapter Patterns) (0) | 2023.02.16 |