디자인패턴_Composite Pattern of Structural Pattern

2021. 1. 20. 11:242021/JOB DA STUDY

Composite Pattern

여러개의 객체들로 구성된 복합 객체와 단일 객체를 클라이언트에서 구별없이 다루게 해주는 패턴

 

  • 전체 - 부분(Directory-File) 관계를 갖는 객체들 사이의 관계 정의시 유용
  • 클라이언트는 전체와 부분을 구분하지 않고, 동일한 인터페이스를 사용

 

  • Component

- 구체적인 부분

- Leaf 클래스와 전체에 해당하는 Composite 클래스에 공통 인터페이스 정의

 

  • Leaf

- 구체적인 부분 클래스

- Composite 객체의 부품으로 설정

  • Composite

- 전체 클래스

- 복수 개의 Component를 갖도록 정의 (복수 개의 Leaf, 복수 개의 Composite 객체를 부분으로 가질 수 있다.)

 

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

  • Computer 클래스: 합성 관계
  • Keyboard 클래스: 데이터 입력 받음
  • Body 클래스: 데이터 처리
  • Monitor 클래스: 처리 결과 출력
합성관계
- 생성자에서 필드에 대한 객체를 생성하는 경우
- 전체 객체의 라이프 타임과 부분 객체의 라이프 타임은 의존적 (전체 객체가 없어지면, 부분 객체도 없어진다.)

 

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

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

    public int getPrice() {return price;}
    public int getPower() {return power;}
}

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

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

    public int getPrice() {return price;}
    public int getPower() {return power;}
}

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

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

    public int getPrice() {return price;}
    public int getPower() {return power;}
}
public class Computer{
    private Keyboard Keyboard;
    private Body body;
    private Monitor monitor;

    public addKeyboard(Keyboard keyboard) { this.keyboard = keyboard; }
    public addBody(Body body) { this.body = body; }
    public addMonitor(Monitor monitor) { this.monitor = monitor; }

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

    public int getPower(){
        int keyboardPower = keyboard.getPower();
        int bodyPower = body.getPower();
        int monitorPower = monitor.getPower();
        return keyboardPower + bodyPower + monitorPower;
    }
}
public class Client{
    public static void main(String[] args){
        //컴퓨터의 부품으로 Keyboard, Body, Motitor 객체를 생성
        Keyboard keyboard = new Keyboard(5, 2);
        Body body = new Body(100, 70);
        Moditor monitor = new Monitor(20, 30);

        //Computer 객체를 생성하고, 부품 객체를 설정
        Computer computer = new Computer();
        computer.addKeyboard(keyboard);
        computer.addBody(body);
        computer.addMonitor(monitor);

        //컴퓨터의 가격과 전력 소비량 구하기
        int computerPrice = computer.getPrice();
        int computerPower = computer.getPower();
        System.out.println("Computer Price" + computerPrice + "만원");
        System.out.println("Computer Power" + computerPower + "W");
        }
    }

 

문제점

  • 다름 부품이 추가되는 경우

 

 

public class Speaker{
    private int price;
    private int power;
    public Speaker(int power, int price){
        this.power = power;
        this.price = price;
    }
    public int getPrice() { return price; }
    public int getPower() { return power; }
}
public class Computer{
    private Speaker speaker; //추가
    private Keyboard Keyboard;
    private Body body;
    private Monitor monitor;

    public addSpeaker(Speaker speaker) { this.speaker = speaker; }  //추가
    public addKeyboard(Keyboard keyboard) { this.keyboard = keyboard; }
    public addBody(Body body) { this.body = body; }
    public addMonitor(Monitor monitor) { this.monitor = monitor; }

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

        return speakerPrice + keyboardPrice + bodyPrice + monitorPrice;
    }

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

        return speakerPower + keyboardPower + bodyPower + monitorPower;
    }
}

*** 위와 같은 방식의 설계는 확장성이 좋지 않다. 따라서 OCP를 만족하지 않는다. ***

 

새로운 부품 추가가 있을 때 마다

  • 새로운 부품에 대한 참조를 필드로 추가
  • 새로운 부품 객체를 설정하는 setter 메서드로 addDevice와 같은 메서드 추가
  • getPrice, getPower 등과 같은 컴퓨터의 부품을 이용하는 모든 메서드에서는 새롭게 추가된 부품 객체를 이용할 수 있도록 수정

해결책

 

  • ComputerDevice 클래스

- ComputerDevice 클래스는 구체적인 부품 클래스의 '공통 기능'만 가지며, 실제 존재하는 구체적인 부품은 될 수 없다. 

- ComputerDevice 객체를 실제로 생성할 수 없으므로 ComputerDevice 클래스는 추상 클래스가 된다.

 

  • 구체적인 부품 클래스 (Keyboard, Body, Monitor, Speaker)

- ComputerDevice 클래스의 하위 클래스

 

  • Computer 클래스

- Computer 클래스는 복수 개의 ComputerDevice 객체를 갖는다.

- ComputerDevice 클래스의 하위 클래스

- ComputerDevice 클래스를 이용하면 Client 프로그램은 Keyboard, Body 등 처럼 Computer 이용 가능

 

public abstract class ComputerDevice{
    public abstract int getPrice();
    public abstract int getPower();
}
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; }
}

public class Body 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; }
}

public class Monitor 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; }
}
public class Computer extends ComputerDevice{
    //복수 개의 ComputerDevice 객체를 가리킴
    private List<ComputerDevice> components = new ArrayList<ComputerDevice>();

    //ComputerDevice 객체를 Computer 클래스에 추가
    public addComponent(ComputerDevice component) { components.add(component); }
    //ComputerDevice 객체를 Computer 클래스에서 제거
    public 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){
            price += component.getPower();
        }
        return power;
    }
}
public class Client{
    public static void main(String[] args){

        //컴퓨터의 부품으로 Keyboard, Body, Monitor 객체를 생성
        Keyboard keyboard = new Keyboard(5, 2);
        Body body = new Body(100, 70);
        Monitor monitor = new Monitor(20, 30);

        //Computer 객체를 생성하고, addComponent()를 통해 부품 객체들을 생성
        Computer computer = new Computer();
        computer.addComponent(keyboard);
        computer.addComponent(body);
        computer.addComponent(monitor);

        //컴퓨터의 가격과 전력 소비량을 구함
        int computerPrice = computer.getPrice();
        int computerPower = computer.getPower();
        System.out.println("Computer Price" + computerPrice + "만원");
        System.out.println("Computer Power" + computerPower + "W");
    }
}

  • Computer Class

- ComputerDevice의 하위 클래스이면서, 복수 개의 ComputerDevice를 갖도록 설계

- addComponent() 메서드를 통해 구체적인 부품인 Keyboard, Body 등을 Computer 클래스의 부품으로 설정

 

  • Client Class

- addComponent() 메서드를 통해 부품의 종류에 관계없이 동일한 메서드로 부품 추가

 

*** Computer Class는 OCP를 준수한다. ***
새로운 부품을 추가하면 ComputerDevice 클래스의 하위 클래스로 구현

Composite Pattern을 이용하면, 부분 객체의 추가/삭제 등이 있어도 전체 객체의 클래스 코드를 변경하지 않아도 된다. 따라서 전체 - 부분 관계를 갖는 객체들 사이의 관계를 정의할 때 유용하다.