목차

Composite pattern

🗓️

Composite 패턴

  • 컴포짓 패턴은 part-whole의 관계를 갖는 객체들을 정의할때 유용하다.
  • 여러 객체가 한 클래스 전체 객체의 일부분으로 정의될때 사용한다. 부분 객체의 추가나 삭제가 있어도 전체 클래스의 코드를 변경하지 않으면 컴포짓 패턴은 유용하다.
  • 그리고 클라이언트는 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의 하위 클래스로 구현하면된다.