Decorator Pattern
Overviewβ
π λ°μ½λ μ΄ν° ν¨ν΄μ λμ μΌλ‘ λ°νμμ λΆκ°κΈ°λ₯μ μΆκ°ν μ μκ²νλ λμμΈ ν¨ν΄μ΄λ€.
When to useβ
νΉμ μ¬μ©μ (Client) κ° λκΈμ λ€λ μλΉμ€κ° μλ€κ³ νμ.
μ΄ λ, λκΈ λμ λΆμ ".." κ°μ λ¬Έμμ΄μ μλμΌλ‘ trim μ²λ¦¬κ° νμνλ€λ μꡬμ¬νμ΄ μμ΄
TrimmingCommentService ν΄λμ€λ₯Ό μμλ°μ ꡬμ±νλ€.
Client.javaβ
@Component
@Getter
public class Client {
private CommentService commentService;
@Autowired
public Client(CommentService commentService) {
this.commentService = commentService;
}
public void writeComment(String comment) {
commentService.addComment(comment);
}
}
CommentService.javaβ
@Component
public class CommentService {
private List<String> comments = new ArrayList();
public void addComment(String comment) {
this.comments.add(comment);
}
}
TrimmingCommentService.javaβ
public class TrimmingCommentService extends CommentService {
@Override
public void addComment(String comment) {
super.addComment(getTrimmedComment(comment));
}
// λΆλͺ
ν΄λΌμ΄μΈνΈ μ½λ λ³κ²½ μμ΄ μλ‘μ΄ κΈ°λ₯ νμ₯μ λλͺ¨νμ§λ§
// Compile time μ μ΄λ―Έ μ΄ κΈ°λ₯μ μλ²½ν fix ν΄μΌλ§ νλ€λκ² λ¨μ μ΄λ€.
// μ μ°νμ§ μλ€. Runtime μμ λ°κΏμΌνλ€λ©΄?
private String getTrimmedComment(String comment) {
return comment.replace("..","");
}
}
νμ¬κΉμ§μ ꡬν λ΄μμ ν΄λμ€ λ€μ΄μ΄κ·Έλ¨μΌλ‘ νννλ©΄ λ€μκ³Ό κ°λ€.
ν΄λΌμ΄μΈνΈμμ λ€μκ³Ό κ°μ΄ Trimming λ λκΈμ μμ±νλ μλΉμ€λ₯Ό μ¬μ©ν μ μμ κ²μ΄λ€.
ClientCommentTest.javaβ
import m.falcon.designpattern.domain.comment.service.TrimmingCommentService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
class ClientCommentTest {
@DisplayName("λκΈ λ¬κΈ° .. μ κ±° ν
μ€νΈ")
@Test
void writeTrimCommentTest() {
// μμ±μ DI
// λ§μ½, spam filtering μ΄ νμνλ€λ©΄?
Client client = new Client(new TrimmingCommentService());
client.writeComment("..μΉ΄λ₯΄λ§..");
String trimmedComment = "μΉ΄λ₯΄λ§";
assertThat(client.getCommentService().getComments().get(0)).isEqualTo(trimmedComment);
}
}
λ¬Έμ μ β
λ§μ½ "Http" κ° ν¬ν¨λ λκΈ μμ±μ λ°©μ§νλ νν°λ§μ΄ νμνλ€λ©΄ μ΄λ»κ² ν΄μΌν κΉ?
μμμ ν΅ν΄ SpamFilteringCommentService λ₯Ό ꡬννλ©΄ λ κ²μ΄λ€.
HttpFilterCommentService.javaβ
public class HttpFilterCommentService extends CommentService {
@Override
public void addComment(String comment) {
if (isSpamComment(comment)) {
return;
}
super.addComment(comment);
}
private Boolean isSpamComment(String comment) {
return comment.contains("http://");
}
}
κ·Έλ λ€λ©΄ ν΄λΌμ΄μΈνΈλ μμ±μ μ£Όμ μ λ€μκ³Ό κ°μ΄ λ³κ²½νμ¬ μ¬μ©ν΄μΌνλ€.
Client client = new Client(new HttpFilterCommentService());
client.writeComment("http://karma.com");
λ λμκ°μ, Http νν°λ§κ³Ό Trimming μ μ± μ λμμ μ μ©νκ³ μΆλ€.
κ²½μ°μ λ°λΌμλ λμ€ ν μ μ± λ§ μ μ©νκ³ μΆλ€. μ΄λ»κ² ν΄μΌν κΉ?
1. μμ ν΄λμ€ μΆκ°β
μμκ³Ό μμ±μ μ£Όμ
μ ν΅ν΄ λ¬Έμ λ₯Ό ν΄κ²°νλ λ°©λ²μ΄λ€.
ν΄λμ€ λ€μ΄μ΄κ·Έλ¨μ ννν΄λ³΄λ©΄ λ€μκ³Ό κ°μ΄ νμν μ μ±
μ΄ λμ΄λ λλ§λ€ ν΄λμ€ μΆκ°κ° λΆκ°νΌνλ€.
μλ°μμ μμμ λ¨μΌ μμλ§ κ°λ₯νκ³ , μ»΄νμΌ νμμ μ μ± μ€μ μ΄ μλ£λμ΄μΌ νκΈ° λλ¬Έμ μ μ°μ±μ΄ λ¨μ΄μ§λ€.
π‘ Decorator ν¨ν΄μ μ΄λ¬ν μν©μ λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ μ‘΄μ¬νλ€.
π λ°μ½λ μ΄ν° ν¨ν΄μ λ°νμμ μ μ± μ λ³κ²½νμ¬ λ€λ₯΄κ² λμνλ μ μ°ν μ½λ μμ±μ λλ ν¨ν΄μ΄λ€.
Why to useβ
λ°νμμ λμ μΌλ‘ λ€λ₯Έ μλΉμ€ (κΈ°λ₯)μ μ¬μ©ν μ μλλ‘ νκ² νκΈ°μν΄.
How to useβ
νΉμ Component λ₯Ό νλ Decorator λ₯Ό Interface λλ abstract ν΄λμ€λ‘ λ§λ€κ³
ꡬ체 Decorator ν΄λμ€λ₯Ό νλμ© μΆκ°νλ€.
μ΄ μμ μμλ CommentService
κ° Component λ€.
CommentService.javaβ
public interface CommentService {
void addComment(String comment);
void printAllComments();
}
DefaultCommentService.javaβ
@Component
@Getter
public class DefaultCommentService implements CommentService {
private List<String> comments = new ArrayList();
@Override
public void addComment(String comment) {
this.comments.add(comment);
}
@Override
public void printAllComments() {
for (var comment : comments) {
System.out.println(comment);
}
}
}
CommentDecorator.javaβ
@Component
@RequiredArgsConstructor
public abstract class CommentDecorator implements CommentService {
// ꡬνμ²΄κ° μλ Interface(μν ) μλ§ μμ‘΄ => DIP μμΉ μ€μ
private final CommentService commentService;
@Override
public void addComment(String comment) {
this.commentService.addComment(comment);
}
@Override
public void printAllComments() {
this.commentService.printAllComments();
}
}
HttpFilteringCommentDecorator.javaβ
public class HttpFilteringCommentDecorator extends CommentDecorator {
public HttpFilteringCommentDecorator(CommentService commentService) {
super(commentService);
}
@Override
public void addComment(String comment) {
if (isSpamComment(comment)) {
return;
}
super.addComment(comment);
}
private Boolean isSpamComment(String comment) {
return comment.contains("http://");
}
}
TrimmingCommentDecorator.javaβ
public class TrimmingCommentDecorator extends CommentDecorator {
public TrimmingCommentDecorator(CommentService commentService) {
super(commentService);
}
@Override
public void addComment(String comment) {
super.addComment(trimComment(comment));
}
private String trimComment(String comment) {
return comment.replace("..", "");
}
}
ClientTest.javaβ
class ClientCommentTest {
private CommentService commentService = new DefaultCommentService();
private static boolean enabledHttpFilter = true;
private static boolean enabledTrimFilter = true;
@DisplayName("Http λ° Trim νν° λμ μ μ©")
@Test
void dynamicCommentPolicyApplyTest() {
// π‘Decorator λ₯Ό ν΅ν μμ±μ μ£Όμ
μΌλ‘ Http, Trimming λμ νν° μ μ©
if (enabledHttpFilter) {
commentService = new HttpFilteringCommentDecorator(commentService);
}
if (enabledTrimFilter) {
commentService = new TrimmingCommentDecorator(commentService);
}
Client client = new Client(commentService);
client.writeComment("μΉ΄λ₯΄λ§..");
client.writeComment("http://karma.com");
client.writeComment("https://karma.com");
client.getCommentService().printAllComments();
}
}
μΆλ ₯ κ²°κ³Όβ
Http, Trimming νν° λͺ¨λ μ μ©λ κ²μ νμΈν μ μλ€.
μΉ΄λ₯΄λ§
https://karma.com
μ΄μ²λΌ λ°νμ λ΄μ λμ μΌλ‘ νν°λ§ μ μ± μ μ μ©ν μλ, μ μ©νμ§ μμ μλ μλ€.
λ΄λΆ λμ μ€λͺ μμβ
μ΄λ»κ² νλμ κ°μ²΄λ‘λΆν° ν λ²μ addComment λ©μλ νΈμΆλ‘ μ¬λ¬ μ μ±
μ λͺ¨λ μ μ©ν μ μλ κ²μΌκΉ?
μμκ³Ό ν¨κ» νν€μ³λ³΄μ
Pros and Consβ
μ₯μ β
- μλ‘μ΄ ν΄λμ€ μμ± μμ΄ κΈ°μ‘΄ κΈ°λ₯ μ‘°ν©
ex) HttpFilterDecorator + TrimmingFilterDecorator μ‘°ν©
μ‘°ν©μ΄ λΆκ°λ₯νλ€λ©΄ ν ν΄λμ€ λ΄μ 2κ°μ§ μ΄μμ νν°λ₯Ό κ°μ΄ κ±Έμ΄μΌν¨. - λ°νμμ λμ μΌλ‘ κΈ°λ₯ κ΅μ²΄
λ¨μ β
- λ°μ½λ μ΄ν° μ‘°ν© μ½λ 볡μ‘μ± μ¦κ°
λ°μ½λ μ΄ν° ν¨ν΄μ μ¬μ©νμ§ μμ λ μλΈ ν΄λμ€ μκ° μΌλ‘ λμ΄λ μ μκΈ° λλ¬Έμ λ¨μ μ΄λΌ 보기μ λ―Όλ§ν¨.
SRP μλ° μ½λβ
if (enabledHttpFilter && enabledTrimFilter) {
// μ μ μΌλ‘ λͺ¨λ μ‘°ν©μ λν μμ ν΄λμ€κ° μ‘΄μ¬ν΄μΌν¨ => μλΈ ν΄λμ€ μ κΈκ²©νκ² μ¦κ° μν
// μ¬λ¬ μ±
μμ κ°λ μλΈ ν΄λμ€ μμ±μ Single Responsibility Principal (SRP) μλ°°
commentService = new HttpFilterAndTrimmingComment(); // λΆλ¦¬λ μ μλ Filter λ‘μ§ 2κ°λ₯Ό νλμ ν΄λμ€κ° λ¬Άμ ννλ‘ κ°μ§κ³ μλ€.
}