본문 바로가기

Software Development/디자인 패턴

[디자인 패턴의 정석] 생성패턴 - 빌더 패턴 (Builder)

빌더 패턴 관계

 

 

개발자 yeah의 Developer Story - 디자인 패턴

 

오늘은 저번 포스팅에 이어서 빌더 패턴 을 이야기해보려 합니다.

 

 

 

먼저 빌더 패턴 을 설명하기 전에 복합 객체에 대해 설명을 하도록 하겠습니다.

 

복합 객체란 하나의 객체가 다른 객체를 포함하는 관계 구조를 말합니다.

 

즉 내부적으로 다른 클래스의 객체를 포함하는 것이죠. 

 

 

 

따라서 복합 객체는 객체를 생성하는 과정이 단일 객체보다 복잡 할 수 있습니다.

 

코드로 예를 들어 보겠습니다.

 

/* Cpu 클래스 */
public class Cpu {
    String cpu;

    Cpu(String cpu) { this.cpu = cpu; }
    String getInfo() { return cpu; }
}

 

/* Ram 클래스 */
public class Ram {
    String ram;

    Ram(String ram) { this.ram = ram; }
    public String getInfo() { return ram; }
}

 

/* Graphics 클래스 */
public class Graphics {
    String graphics;

    Graphics(String graphics) { this.graphics = graphics; }
    String getInfo() { return graphics; }
}

 

Computer 를 만드는 데 있어 필요한 부품 Cpu, Ram, Graphics 클래스가 있습니다.

 

 

public class Computer {
    private Cpu cpu;				
    private Ram ram;
    private Graphics graphics;
    
    /* Computer 생성자 */
    Computer(Cpu cpu, Ram ram, Graphics graphics) {
        this.cpu = cpu;
        this.ram = ram;
        this.graphics = graphics;
    }
    
    /* Computer 정보 출력 */
    public void computerInfo() {
        String cpuInfo = cpu.getInfo();
        String ramInfo = ram.getInfo();
        String graphicsInfo = graphics == null ? "내장형 그래픽" : graphics.getInfo();

        System.out.println("컴퓨터 정보 출력 - "
        + "Cpu : " + cpuInfo 
        + ", Ram : " + ramInfo 
        + ", Graphics  : " + graphicsInfo);
    }
}

 

Computer 클래스는 Cpu, Ram, Graphics 를 포함하는 복합 객체입니다. 

 

생성자 를 통한 의존성 주입 으로 객체를 전달받아 set 하고 있습니다.

 

 

public class BuilderPattern {

    public static void main(String[] args) {
        /* 프리미엄 컴퓨터 */
        Cpu cpu = new Cpu("Intel core i7");
        Ram ram = new Ram("16GB");
        Graphics graphics = new Graphics("RTX-3090");

        Computer premiumPC = new Computer(cpu, ram, graphics);
        premiumPC.computerInfo();

        /* 저가 컴퓨터 - 그래픽카드를 포함하지 않음 */
        Cpu cpu = new Cpu("Intel core i7");
        Ram ram = new Ram("8GB");
        Computer lowCostPC = new Computer(cpu, ram, null);
        premiumPC.computerInfo();
    }
}

 

다음은 main() 에서 Computer 를 생성하는 코드입니다.

 

Computer 객체 생성 시 new키워를 통한 생성자에 각각의 Cpu, Ram, Graphics 객체를 전달하는 것을 알 수 있습니다.

 


다음 코드의 문제점은 무엇일까요?

 

Computer 를 만들 때 부품의 구성여부에 따라 종류가 달라질 수 있습니다. 

 

위 코드에서는 그래픽카드 유무에 따라 프리미엄 / 저가형 으로 구분을 하였습니다.

 

하지만 생성자를 통해 생성하기 때문에 저가형 같은 경우 그래픽카드가 필요 없지만 null을 넘기는 것을 확인할 수 있습니다.

 

이는 Computer 객체(복합객체)불필요한 객체 생성을 야기합니다.

 

 

 

그렇다면 복합 객체 내 원하는 객체만 생성하여 인스턴스화 하려면 어떻게 해야 할까요?

 

 

첫번째, 우리는 여러 개의 생성자오버로딩 하여 해결하는 방법을 생각할 수 있습니다.

 

하지만 이 방법은 구성하는 부품의 개수가 많아지면 그때마다 생성자를 추가해줘야 하는 번거로움이 있습니다.

 

 

두번째, Computer 클래스 내부에 각각 부품을 set 할 수 있는 함수를 정의하여 해결할 수도 있습니다.

 

하지만 이 방법 또한 Computer 객체를 생성한 후에 추가로 set 함수들을 호출해야 하기 때문에 누락할 잠재적인 문제가 있습니다.

 

 

※따라서 복잡한 구조를 가진 복합 객체를 우리가 원하는 구조로 생성하기 위해서는 구조에 맞게 생성할 수 있는 로직이 필요합니다.

 


따라서 이러한 문제를 해결할 수 있는 빌더 패턴 을 적용해보도록 하겠습니다.

 

 

빌더 패턴 복합 객체 생성 과정을 별도의 독립된 클래스(Builder) 로 관리합니다.

 

/* 빌더 패턴을 위한 빌더 */
public class Builder {
    private Computer computer;	//Computer 객체

	/* 생성자를 통해 Computer 객체를 먼저 생성 */
    Builder() { computer = new Computer(); }

	/* cpu 정보를 set */
    public Builder setCpu(Cpu cpu) {
        computer.setCpu(cpu);
        return this;
    }
    
    /* Ram 정보를 set */
    public Builder setRam(Ram ram) {
        computer.setRam(ram);
        return this;
    }
    
    /* Graphics 정보를 set */
    public Builder setGraphics(Graphics graphics) {
        computer.setGraphics(graphics);
        return this;
    }

	/* 생성한 computer 객체를 반환 */
    public Computer build() { return computer; }
}

 

Builder 클래스 내부를 먼저 살펴보면, 생성자를 통해 생성된 Computer 가 멤버 변수로 존재합니다.

 

그리고 각각의 구성요소Cpu, Ram, Graphicsset 할 수 있는 함수들이 정의되어 있는데,

 

이 함수들에서 return되는 반환형이 Builder 인 것이 핵심입니다.

 

마지막으로 Computer 객체를 반환하는 build()로 구성되어있습니다.

 

 

 

Builder 를 이용하여 어떻게 위 문제들을 해결 했는지 main() 코드를 살펴보겠습니다.

 

public class BuilderPattern {

    public static void main(String[] args) {
        /* 프리미엄 컴퓨터 */
        Cpu cpu = new Cpu("Intel core i7");
        Ram ram = new Ram("16GB");
        Graphics graphics = new Graphics("RTX-3090");

		/* Builder를 이용하여 원하는 구성으로 set하여 객체를 생성 */
        Builder builder = new Builder();
        Computer premiumCP = builder.setCpu(cpu)
                .setRam(ram)
                .setGraphics(graphics)
                .build();

        premiumCP.computerInfo();

        /* 저가형 컴퓨터 */
        cpu = new Cpu("Intel core i5");
        ram = new Ram("8GB");

		/* Builder를 이용하여 원하는 구성으로 set하여 객체를 생성 */
        builder = new Builder();
        Computer lowCostPc = builder.setCpu(cpu)
        	.setRam(ram)
                .build();

        lowCostPc.computerInfo();
    }
}

 

 

 

먼저 Builder 를 생성하고 Computer 객체 생성을 Builder 를 통해 생성한 것을 알 수 있습니다.

 

main() 을 보시면 Builder set함수 연속해서 호출하는 것을 확인할 수 있습니다.

 

그건 바로 해당 set 함수들의 return Builder 였기 때문에 가능한 것이죠!

 

 

따라서 해당 Builder를 통해 원하는 구성요소로 set을 하고 최종적으로는 build() 를 호출하여 원하는 객체를 생성 할 수 있습니다.

 

 

 

 

이렇게 오늘은 빌더 패턴 을 이야기해봤는데요.

 

궁금하시거나 질문이 있으시면 댓글로 달아주세요 !!

 

 

감사합니다!