
컴포짓(Composite) 패턴은 개별 객체(Leaf)와 그룹 객체(Composite)를 동일하게 처리할 수 있는 계층 구조(Part-Whole Hierarchy)를 설계하는 패턴이다.
클라이언트는 전체(Composite)와 부분(Leaf)을 동일한 인터페이스(Component)로 인식하고 처리할 수 있다.

public class Item {
private String name;
private int price;
public Item(String name, int price) {
this.name = name;
this.price = price;
}
public int getPrice() {
return this.price;
}
}
public class Bag {
private List<Item> items = new ArrayList<>();
public void add(Item item) {
items.add(item);
}
public List<Item> getItems() {
return items;
}
}
public class Client {
public static void main(String[] args) {
Item doranBlade = new Item("도란검", 450);
Item healPotion = new Item("체력 물약", 50);
Bag bag = new Bag();
bag.add(doranBlade);
bag.add(healPotion);
Client client = new Client();
client.printPrice(doranBlade);
client.printPrice(bag);
}
private void printPrice(Item item) {
System.out.println(item.getPrice());
}
private void printPrice(Bag bag) {
int sum = bag.getItems().stream().mapToInt(Item::getPrice).sum();
System.out.println(sum);
}
}
Before 코드 문제점
printPrice() 메서드가 Item과 Bag에 대해 각각 정의되어 있어 중복 코드가 발생한다.
Bag 외에 다른 그룹 객체가 추가되면, 클라이언트는 그 그룹 객체를 처리하는 새로운 메서드를 추가해야 한다.
클라이언트는 Item과 Bag을 동일한 방식으로 처리할 수 없어, 코드의 일관성이 떨어진다.
public interface Component {
int getPrice();
}
public class Item implements Component {
private String name;
private int price;
public Item(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public int getPrice() {
return this.price;
}
}
public class Bag implements Component {
private List<Component> components = new ArrayList<>();
public void add(Component component) {
components.add(component);
}
public List<Component> getComponents() {
return components;
}
@Override
public int getPrice() {
return components.stream().mapToInt(Component::getPrice).sum();
}
}
public class Client {
public static void main(String[] args) {
Item doranBlade = new Item("도란검", 450);
Item healPotion = new Item("체력 물약", 50);
Bag bag = new Bag();
bag.add(doranBlade);
bag.add(healPotion);
Client client = new Client();
client.printPrice(doranBlade);
client.printPrice(bag);
}
private void printPrice(Component component) {
System.out.println(component.getPrice());
}
}

장점
1. 복잡한 트리 구조를 편리하게 사용 가능
2. 다형성과 재귀 활용 가능
(
컴포짓 패턴의 핵심: Composite 안에 또 다른 Composite
Bag (Composite)
├── Item (Leaf) [도란검: 450원]
├── Item (Leaf) [체력 물약: 50원]
└── Bag (Composite)
├── Item (Leaf) [마법 물약: 200원]
└── Item (Leaf) [롱소드: 350원]
첫 번째 Bag(Composite):
도란검과 체력 물약이라는 개별 객체(Leaf)를 포함.두 번째 Bag(Composite):
마법 물약과 롱소드라는 개별 객체(Leaf)를 포함.이처럼 Composite 객체 안에 또 다른 Composite 객체를 포함하여 재귀적인 트리 구조를 쉽게 구현할 수 있다.
)
3. 확장성:
4. 유지보수 용이성:
단점
1. 과도한 일반화:
2. 복잡성 증가:
Swing 라이브러리
JComponent는 JButton, JPanel, JLabel 등 모든 UI 컴포넌트의 공통 인터페이스 역할을 한다.JPanel은 또 다른 JPanel이나 JButton을 포함할 수 있어 재귀적인 트리 구조를 형성한다.예제:
JPanel panel = new JPanel(); // Composite 객체
JButton button = new JButton("Click Me"); // Leaf 객체
panel.add(button); // Composite에 Leaf 추가
JavaServer Faces (JSF)
UIComponent는 모든 JSF UI 컴포넌트의 공통 인터페이스 역할을 하며, 재귀적인 트리 구조를 형성한다.
1. 구조
CommentService:
public class CommentService {
public void addComment(String comment) {
System.out.println(comment);
}
}
SpamFilteringCommentService:
CommentService를 상속받아, 스팸 여부를 확인한 후 댓글을 출력.public class SpamFilteringCommentService extends CommentService {
@Override
public void addComment(String comment) {
if (!isSpam(comment)) {
super.addComment(comment);
}
}
private boolean isSpam(String comment) {
return comment.contains("http");
}
}
TrimmingCommentService:
CommentService를 상속받아, 댓글을 수정한 후 출력.public class TrimmingCommentService extends CommentService {
@Override
public void addComment(String comment) {
super.addComment(trim(comment));
}
private String trim(String comment) {
return comment.replace("...", "");
}
}
Client:
CommentService를 사용해 댓글을 출력.public class Client {
private CommentService commentService;
public Client(CommentService commentService) {
this.commentService = commentService;
}
private void writeComment(String comment) {
commentService.addComment(comment);
}
}
2. 문제점
확장성 부족:
SpamFilteringAndTrimmingCommentService).상속의 단점:
유연성 부족:
1. 구조
CommentService 인터페이스:
public interface CommentService {
void addComment(String comment);
}
DefaultCommentService:
public class DefaultCommentService implements CommentService {
@Override
public void addComment(String comment) {
System.out.println(comment);
}
}
CommentDecorator:
CommentService를 감싸고 위임한다.public class CommentDecorator implements CommentService {
private CommentService commentService;
public CommentDecorator(CommentService commentService) {
this.commentService = commentService;
}
@Override
public void addComment(String comment) {
commentService.addComment(comment);
}
}
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");
}
}
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("...", "");
}
}
Client:
CommentService를 사용하며, 데코레이터로 기능을 조합.public class Client {
private CommentService commentService;
public Client(CommentService commentService) {
this.commentService = commentService;
}
public void writeComment(String comment) {
commentService.addComment(comment);
}
}
App:
CommentService를 생성.public class App {
private static boolean enabledSpamFilter = true;
private static boolean enabledTrimming = true;
public static void main(String[] args) {
CommentService commentService = new DefaultCommentService();
if (enabledSpamFilter) {
commentService = new SpamFilteringCommentDecorator(commentService);
}
if (enabledTrimming) {
commentService = new TrimmingCommentDecorator(commentService);
}
Client client = new Client(commentService);
client.writeComment("오징어게임");
client.writeComment("보는게 하는거 보다 재밌을 수가 없지...");
client.writeComment("http://whiteship.me");
}
}

2. 개선점
확장성 증가:
CommentDecorator를 상속받아 새로운 클래스를 구현하면 됨.유연성 증가:
SpamFilteringCommentDecorator와 TrimmingCommentDecorator를 필요에 따라 조합.상속 대신 위임:
클래스 폭발 문제 해결:
입력:
"오징어게임"
"보는게 하는거 보다 재밌을 수가 없지..."
"http://whiteship.me"
출력 (스팸 필터링 + Trimming 데코레이터 적용):
오징어게임
보는게 하는거 보다 재밌을 수가 없지
장점
기존 코드 수정 없이 새로운 기능 추가 가능:
DefaultCommentService)를 수정하지 않고도 새로운 데코레이터를 추가 가능.유연한 기능 조합:
단일 책임 원칙 준수(SRP):
단점
복잡성 증가:
위임 호출 오버헤드:
자바 I/O
InputStream과 OutputStream 계열 클래스.BufferedInputStream, DataInputStream 등은 데코레이터를 활용.스프링
GUI 프레임워크
Decorator의 핵심
Decorator가 필요한 이유
Decorator 패턴의 비유
아메리카노: 기본 클래스아메리카노 + 우유: 새 클래스 생성아메리카노 + 초콜릿: 또 다른 새 클래스 생성아메리카노 + 우유 + 초콜릿 + 휘핑크림: 또 다른 새 클래스 생성코드 예시:
// 기본 음료
public interface Beverage {
String getDescription();
double getCost();
}
// 기본 아메리카노
public class Americano implements Beverage {
@Override
public String getDescription() {
return "아메리카노";
}
@Override
public double getCost() {
return 3000; // 기본 가격
}
}
// 데코레이터: 우유 추가
public class MilkDecorator implements Beverage {
private Beverage beverage;
public MilkDecorator(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", 우유";
}
@Override
public double getCost() {
return beverage.getCost() + 500; // 우유 추가 가격
}
}
// 데코레이터: 초콜릿 추가
public class ChocolateDecorator implements Beverage {
private Beverage beverage;
public ChocolateDecorator(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", 초콜릿";
}
@Override
public double getCost() {
return beverage.getCost() + 700; // 초콜릿 추가 가격
}
}
// 데코레이터 사용
public class Cafe {
public static void main(String[] args) {
Beverage beverage = new Americano(); // 기본 아메리카노
beverage = new MilkDecorator(beverage); // 우유 추가
beverage = new ChocolateDecorator(beverage); // 초콜릿 추가
System.out.println(beverage.getDescription() + " 가격: " + beverage.getCost());
// 출력: 아메리카노, 우유, 초콜릿 가격: 4200
}
}
Decorator의 동작 방식
Americano).new MilkDecorator(beverage).beverage)의 메서드를 호출하고, 부가 기능을 추가로 수행한다.Decorator와 Before 코드의 차이
SpamFilteringCommentService와 TrimmingCommentService는 각각 CommentService를 상속하여 기능을 확장.SpamFilteringAndTrimmingCommentService 등의 추가 클래스가 필요.CommentService commentService = new DefaultCommentService();
commentService = new SpamFilteringCommentDecorator(commentService);
commentService = new TrimmingCommentDecorator(commentService);