여러 객체가 한 클래스 전체 객체의 일부분으로 정의될때 사용한다. 부분 객체의 추가나 삭제가 있어도 전체 클래스의 코드를 변경하지 않으면 컴포짓 패턴은 유용하다.
그리고 클라이언트는 whole과 part를 구분하지 않고 동일한 인터페이스를 가질 수 있다.
컴퓨터에 추가 장치 지원하기
컴퓨터를 모델링해보자. 컴퓨터에는 Keyboard, Monitor, RAM, SSD, CPU등이 있다. 각각의 클래스를 정의하고 Computer 클래스는 이들 구성 장치를 포함하는것으로 구성할 수 있다.
public class CPU {
private int price;
private int power;
public CPU(int power, int price) {
this.power = power;
this.price = price;
}
public int getPrice() {
return price;
}
public int getPower() {
return power;
}
}
public class SSD {
// 생략
}
public class VGA {
// 생략
}
public class Computer {
private CPU cpu;
private VGA vga;
private SSD ssd;
public void addCPU(CPU cpu) {
this.cpu = cpu;
}
public void addVGA(VGA vga) {
this.vga = vga;
}
public void addSSD(SSD ssd) {
this.ssd = ssd;
}
public int getPrice() {
int cpuPrice = cpu.getPrice();
int vgaPrice = vga.getPrice();
int ssdPrice = ssd.getPrice();
return cpuPrice + vgaPrice + ssdPrice;
}
public int getoPower() {
int cpuPower = cpu.getPower();
int vgaPower = vga.getPower();
int ssdPower = ssd.getPower();
return cpuPower + vgaPower + ssdPower;
}
}
public class Client {
public static void main(String[] args) {
CPU amd5800x = new CPU(105, 60);
VGA gtx3080 = new VGA(200, 200);
SSD wd500 = new SSD(5, 15);
Computer computer = new Computer();
computer.addCPU(amd5800x);
computer.addVGA(gtx3080);
computer.addSSD(wd500);
int computerPrice = computer.getPrice();
int computerPower = computer.getoPower();
System.out.println("Total price: " + computerPrice);
System.out.println("Consume power: " + computerPower);
}
}
문제점
다른 부품을 추가할 때는 어떻게?
만약 새로운 부품을 추가하려면 다음과 같이 수정해야한다.
새로운 붚무에 대한 참조를 필드로 추가한다
새로운 부품에 객체를 설정하는 setter로 addHDD같은 메소드를 추가한다
부품의 메소드를 이용하는 모든 메소드에서 새로 추가된 부품 객체를 추가한다.
새로운 부품이 추가될때마다 기존 코드를 지속적으로 수정해야한다.
해결책
앞의 코드는 부품의 변화에 따라 Computer클래스의 코드도 변화할 수 밖에 없는 문제를 갖고 있다.
구체적인 부품들을 일반화한 클래스를 정의하고 이를 Computer클래스가 가리키게 하는 것이 올바른 설계다.
주요 개선점은 다음과 같다
Computer가 가질 수 있는 부품들을 일반화해 ComputerDevice라는 클래스를 정의한다.
ComputerDevice클래스는 구체적인 부품 클래스의 공통 기능만 가진다.
ComputerDevice 객체를 실재로 인스턴스 할 수 없다. (추상 클래스)
구체적인 부품은 ComputerDevice를 구현한다.
Computer클래스 역시 ComputerDevice의 하위 클래스다.
public abstract class ComputerDevice {
public abstract int getPrice();
public abstract int getPower();
}
public class CPU extends ComputerDevice {
//...
}
public class VGA extends ComputerDevice {
//...
}
public class SSD extends ComputerDevice {
//...
}
public class Computer extends ComputerDevice {
private List<ComputerDevice> components = new ArrayList<ComputerDevice>();
public void addComponent(ComputerDevice component) {
components.add(component);
}
public void removeComponent(ComputerDevice component) {
components.remove(component);
}
public int getPrice() {
int price = 0;
for (ComputerDevice component : components) {
price += component.getPrice();
}
return price;
}
public int getPower() {
int power = 0;
for (ComputerDevice component : components) {
power += component.getPower();
}
return power;
}
}
이제 Computer클래스에 새로운 부품을 추가해 Computer를 확장하려고 할 때 Computer클래스 코드는 변경할 필요가 없다.
일반화된 부품을 의미하는 ComputerDevice를 이용하기 때문이다.
새로운 부품인 NIC를 추가하더라도 ComputerDevice의 하위 클래스로 구현하면된다.