컴포지트 패턴은 동일 기능을 수행하는 부분-전체 관계를 정의할 때 유용하다.
Context
보통 컴퓨터의 가격과 전력을 산정할 때는 중요하다. 어떤 CPU와 어떤 램 그리고 그래픽카드를 장착했는지에 따라 컴퓨터의 가격과 소비전력을 천차만별이다.
흔히 우리는 기존에 배운대로 설계하여 코드를 작성할 것이다. 일부 눈엣가시인 듯한 코드들이 보이지만 가볍게 무시하자.
class Computer {
private CPU cpu;
private Ram ram;
private HardDisk disk;
public Computer(CPU cpu, Ram ram, HardDisk disk) {
this.cpu = cpu;
this.ram = ram;
this.disk = disk;
}
public int getPrice () {
return cpu.getPrice() + ram.getPrice() + disk.getPrice();
}
public int getPower() {
return cpu.getPower() + ram.getPower() + disk.getPower();
}
}
class CPU {
public int getPrice () {
return 100;
}
public int getPower() {
return 95;
}
}
class Ram {
public int getPrice () {
return 20;
}
public int getPower () {
return 5;
}
}
class HardDisk {
public int getPrice() {
return 10;
}
public int getPower() {
return 20;
}
}
Problem
역시나 요구사항은 피할 수 없었다. 게임을 하고싶은데 서버용 컴퓨터처럼 그래픽카드가 장착되어 있지 않았다.
위 다이어그램처럼 VGA
를 추가해주자.
class VGA {
public int getPrice() {
return 15;
}
public int getPower() {
return 60;
}
}
VGA
클래스가 추가되었으므로 Computer
에 장착하려면 수정이 불가피하다.
class Computer {
private CPU cpu;
private Ram ram;
private HardDisk disk;
private VGA vga;
public Computer(CPU cpu, Ram ram, HardDisk disk, VGA vga) {
this.cpu = cpu;
this.ram = ram;
this.disk = disk;
this.vga = vga;
}
public int getPrice () {
return cpu.getPrice() + ram.getPrice() + disk.getPrice() + vga.getPrice();
}
public int getPower() {
return cpu.getPower() + ram.getPower() + disk.getPower() + vga.getPower();
}
}
필드로 vga:VGA
를 넣고 각 메서드에 해당 내용을 수정해주었다. 확장에 대해서 수정이 되었으니 역시 OCP 위반이다.
더불어 이러한 단순 코드들은 별로 좋지 않은 코드들이다.
Solution
Computer
은 각 부품에 대해서 포함관계를 가지고 있으며 각 부품과 동일하게 getPower
, getPrice
등의 기능을 가지고 있다.
이런 경우 아래와 같은 컴포지트 패턴을 사용하면 유용하게 처리할 수 있다.
전체-부분 관계는 사실 부분과 전체는 모두 동일한 기능을 수행할 수 있다. 부분들이 모두 같은 기능을 수행한다는 것은 전체가 그 기능을 수행한 것과 같은 의미이기 떄문이다.
abstract class Component {
public abstract int getPrice();
public abstract int getPower();
}
class Computer extends Component {
private List<Component> components = new ArrayList<>();
@Override
public int getPrice() {
return components.stream().mapToInt(Component::getPrice).sum();
}
@Override
public int getPower() {
return components.stream().mapToInt(Component::getPower).sum();
}
public void addComponent(Component component) {
components.add(component);
}
}
class CPU extends Component {
@Override
public int getPrice() {
return 100;
}
@Override
public int getPower() {
return 95;
}
}
class Ram extends Component {
@Override
public int getPrice() {
return 20;
}
@Override
public int getPower() {
return 5;
}
}
class HardDisk extends Component {
@Override
public int getPrice() {
return 10;
}
@Override
public int getPower() {
return 20;
}
}
class VGA extends Component {
@Override
public int getPrice() {
return 15;
}
@Override
public int getPower() {
return 60;
}
}
코드를 작성한 후 아래와 같이 테스트해보면 잘 수행하는 것을 알 수 있으며 VGA
추가에 따른 기존 코드의 변화는 없는 것을
알 수 있다.
class Client{
public static void main(String[] args) {
Computer computer = new Computer();
computer.addComponent(new CPU());
computer.addComponent(new HardDisk());
computer.addComponent(new Ram());
computer.addComponent(new VGA());
System.out.println(computer.getPower());
System.out.println(computer.getPrice());
}
}
Summary
컴포지트 패턴은 부분-전체 관계를 정의할 때 유용하며, 클라이언트는 전체와 부분을 구분하지 않고 사용할 수 있다.
구성 요소
- Component : 전체와 부분에 해당하는 공통 인터페이스를 정의한다.
- Composite : 전체 클래스로 복수 개의 Component를 가질 수 있다.
- Leaf : 구체적인 부분 클래스로 Composite 객체의 부품으로 사용된다.
'소프트웨어 > 디자인패턴' 카테고리의 다른 글
[소프트웨어/디자인패턴] 방문자 패턴(Visitor Pattern) (0) | 2018.05.21 |
---|---|
[소프트웨어/디자인패턴] 추상 팩토리 패턴 (Abstract Factory Pattern) (0) | 2017.02.28 |
[소프트웨어/디자인패턴] 퍼사드 패턴 (Facade Pattern) (0) | 2017.02.24 |
[소프트웨어/디자인패턴] 어댑터 패턴 (Adapter Pattern) (0) | 2017.02.24 |
[소프트웨어/디자인패턴] 팩토리 메서드 패턴(Factory Method Pattern) (0) | 2017.01.27 |