Priv's Blog

14. 컴퍼지트 패턴 본문

Dev. Study Note/Design Pattern

14. 컴퍼지트 패턴

Priv 2022. 8. 12. 15:38


 

 

1. 컴퓨터에 추가 장치 지원하기

컴퓨터는 키보드, 본체, 모니터가 존재한다.

컴퓨터를 구성하는 장치인 이 3가지 클래스(Keyboard, Body, Monitor)는 Computer 클래스가 포함하고 있다.

또한 컴퓨터의 가격을 결정하는 price 변수, 전력 소비량을 결정하는 power 변수가 존재하며, 이러한 속성과 연산이 존재한다.

키보드, 본체, 모니터는 각각 자신의 가격과 전력 소비량을 속성으로 가진다.

컴퓨터의 가격과 전력 소비량은 부품에 따라 계산되기 때문에 이를 위한 별도의 속성은 정의하지 않는다.

Computer 클래스는 Keyboard, Body, Monitor 클래스의 객체를 부분으로 가진다.

즉, 각 객체의 참조를 필드로 정의할 필요가 있다.

package P14;

public class Keyboard {
    private int price;
    private int power;

    
    public Keyboard(int power, int price) {
        this.power = power;
        this.price = price;
    }

    public int getPrice() {
        return price;
    }

    public int getPower() {
        return power;
    }
}
package P14;

public class Body {
    private int price;
    private int power;

    
    public Body(int power, int price) {
        this.power = power;
        this.price = price;
    }

    public int getPrice() {
        return price;
    }

    public int getPower() {
        return power;
    }
}
package P14;

public class Monitor {
    private int price;
    private int power;

    
    public Monitor(int power, int price) {
        this.power = power;
        this.price = price;
    }

    public int getPrice() {
        return price;
    }

    public int getPower() {
        return power;
    }
}
package P14;

public class Computer {
    private Body body;
    private Keyboard keyboard;
    private Monitor monitor;


    public void addBody(Body body) {
        this.body = body;
    }

    public void addkeyBoard(Keyboard keyboard) {
        this.keyboard = keyboard;
    }

    public void addMonitor(Monitor monitor) {
        this.monitor = monitor;
    }

    public int getPrice() {
        int bodyPrice = body.getPrice();
        int keyboardPrice = keyboard.getPrice();
        int monitorPrice = monitor.getPrice();

        return bodyPrice + keyboardPrice + monitorPrice;
    }

    public int getPower() {
        int bodyPower = body.getPower();
        int keyboardPower = keyboard.getPower();
        int monitorPower = monitor.getPower();

        return bodyPower + keyboardPower + monitorPower;
    }
}

 


 

2. 문제점

만약 Computer 클래스의 부품으로 Speaker 클래스가 추가된다면 어떻게 해야 할까?

기존의 Computer 클래스를 확장해야 하므로, Body, Keyboard, Monitor 클래스와 동일하게 Computer 클래스의 부품으로 정의하면 될 것이다.

즉, Speaker 클래스를 정의하고, 합성 관계로 Computer 클래스가 부분으로써 표현되도록 만든다.

그림 14-4와 같은 상황일 경우, 컴퓨터의 가격, 소비 전력량을 계산할 때 필요한 스피커의 가격과 소비 전력량도 구해야 한다.

이를 위해서는 Computer 클래스가 Speaker 객체를 가질 수 있도록 코드를 수정해야 한다.

하지만 이와 같은 방식은 좋은 방식이 아니다.

새로운 부품을 위한 클래스를 추가할 때마다 코드를 수정해야 하기 때문이다.

이는 OCP를 위반하는 것이며, 코드의 확장성도 떨어진다.

즉, Computer 클래스에 부품을 추가할 때, Computer 클래스의 코드는 변경되지 않아야 하는 것이 목표인 것이다.

package P14;

public class Speaker {
    private int price;
    private int power;

    public Speaker(int power, int price) {
        this.price = price;
        this.power = power;
    }

    public int getPrice() {
        return price;
    }

    public int getPower() {
        return power;
    }
}
package P14;

public class Computer {
    private Body body;
    private Keyboard keyboard;
    private Monitor monitor;
    private Speaker speaker;


    public void addBody(Body body) {
        this.body = body;
    }

    public void addkeyBoard(Keyboard keyboard) {
        this.keyboard = keyboard;
    }

    public void addMonitor(Monitor monitor) {
        this.monitor = monitor;
    }

    public int getPrice() {
        int bodyPrice = body.getPrice();
        int keyboardPrice = keyboard.getPrice();
        int monitorPrice = monitor.getPrice();
        int speakerPrice = speaker.getPrice();

        return bodyPrice + keyboardPrice + monitorPrice + speakerPrice;
    }

    public int getPower() {
        int bodyPower = body.getPower();
        int keyboardPower = keyboard.getPower();
        int monitorPower = monitor.getPower();
        int speakerPower = speaker.getPower();

        return bodyPower + keyboardPower + monitorPower + speakerPower;
    }
}

 


 

3. 해결책

Computer 클래스가 Monitor, Body, Keyboard, Speaker 객체 등을 직접 가리키면 부품의 변화에 따라 Computer 클래스의 코드도 변화할 수밖에 없다.

그러므로 구체적인 부품들을 일반화한 클래스로 정의하고, 이를 Computer 클래스가 가리키게 하는 것이 필요하다.

package P14;

public abstract class ComputerDevice {
    public abstract int getPrice();
    public abstract int getPower();
}
package P14;

public class Keyboard extends ComputerDevice {
    private int price;
    private int power;

    
    public Keyboard(int power, int price) {
        this.power = power;
        this.price = price;
    }

    public int getPrice() {
        return price;
    }

    public int getPower() {
        return power;
    }
}
package P14;

import java.util.ArrayList;
import java.util.List;

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;
    }
}

이제 addComponent 메서드처럼 일반화된 이름을 사용함으로써, 부품 종류와 무관하게 동일한 메서드로 부품 추가가 가능해졌다.

이제 OCP를 준수하는 Computer 클래스가 만들어졌다.

즉, 새로운 부품 객체를 추가해 Computer 클래스를 확장할 때, Computer 클래스는 건드릴 필요가 없다.

이는 Body, Monitor 등과 같이 구체적인 부품을 뜻하는 클래스를 Computer 클래스 내부에서 사용하지 않고, 일반화된 부품을 의미하는 ComputerDevice 클래스만 사용하기 때문이다.

이제 ComputerDevice 클래스를 상속받는 클래스를 부품으로 사용하다면, Computer 클래스는 임의의 부품을 추가하면서도 코드 변경이 불필요하다.

만약 스피커를 추가하고자 한다면, Speaker 클래스를 ComputerDevice의 자식으로 구현하면 된다는 것이다.

그림 14-6을 보면 Computer 클래스는 Speaker 클래스와 아무런 관계가 없으며, 오직 ComputerDevice와만 관계가 있음을 알 수 있다.

 


 

4. 컴퍼지트 패턴

컴퍼지트 패턴은 부분-전체의 관계를 가지는 객체들을 정의할 때 사용하는 패턴이다.

컴퓨터 예제에서 알 수 있듯이, Monitor, Body 등의 객체가 Computer 클래스 전체 객체의 일부분으로 정의되었다.

이런 경우에는 부분 객체의 추가, 삭제가 이루어져도 전체 객체의 클래스 코드를 변경하지 않으면 컴퍼지트 패턴은 유용하다.

즉, 컴퍼지트 패턴은 전체-부분의 관계를 가지는 객체들 사이의 관계를 정의할 때 유용하다.

또한 클라이언트는 전체와 부분을 구분하지 않고 동일한 인터페이스를 사용할 수 있다.

컴퍼지트 패턴을 컴퓨터 추가 장치 예제에 적용하면 다음과 같다.

 


 


수고하셨습니다!


'Dev. Study Note > Design Pattern' 카테고리의 다른 글

13. 추상 팩토리 패턴  (0) 2022.07.03
12. 팩토리 메서드 패턴  (0) 2022.07.03
11. 템플릿 메서드 패턴  (0) 2022.07.03
10. 데커레이터 패턴  (0) 2022.07.03
9. 옵서버 패턴  (0) 2022.07.03
Comments