보다 더 나은 내일의 나를 위해

객체 생성을 더 편리하게! <빌더 패턴> 본문

JAVA/다지인 패턴

객체 생성을 더 편리하게! <빌더 패턴>

H-SC 2022. 6. 14. 18:33

개요

자바에서 빌더 패턴(Build Pattern)은 크게 두 가지가 있습니다.

 

1. 이펙티브 자바의 빌더 패턴

2. GoF 디자인 패턴 중 빌더 패턴

 

두 패턴은 관점이 다른데, 여기서 패턴의 차이점이 나타납니다.

 

이펙티브 자바의 빌더 패턴을 필드가 많은 객체를 생성할 때 필요에 따라 생성자나 setter를 많이 만들지 않아도 손쉽게 생성하는데 목적이 있습니다.

그에 반해 GoF의 설명을 보면 다음과 같습니다.

빌더 패턴이란 복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게 하는 패턴이다.
- wikipedia

즉, 객체 생성과 조립 방법을 분리해 객체 생성 절차는 동일하게 하며, 서로 다른 필드를 가진 객체를 생성하는데 목적이 있습니다.

 

 

 

GoF 빌더 패턴 구조

위키피디아

 GoF의 빌더 패턴에서는 상품(Product), 상품 감독(Director), 추상 건축자(Builder),  구체 건축자(Concrete Builder)를 통해 최종 상품(생성하고자 하는 객체)을 만듭니다.

Product를 생성할 때 모두 Director를 사용해 Builder를 통하여 Concrete Builder대로 생성하게 됩니다.

말로 하면 참 어려워보이는데, 코드로 알아봅시다.

 

 

 

GoF 빌더 패턴 예제

/** Product */
class Laptop {
    private String manufacturer;
    private String name;
    
    public void setManufacturer(String manufacturer) {
    	this.manufacturer = manufacturer;
    }
    public void setName(String name) {
    	this.name = name;
    }
}

/** Abstract Builder */
abstract class LaptopBuilder {
    protected Laptop laptop;
    
    public Laptop getLaptop() {
    	return laptop;
    }
    
    public void createLaptop() {
    	laptop = new Laptop();
    }
    
    public abstract void manufacturer();
    public abstract void name();
}

/** ConcreteBuilder */
class SamsungLaptopBuilder extends LaptopBuilder {
    public void manufacturer() {
    	laptop.setManufacturer("삼성");
    }
    public void name() {
    	laptop.setName("갤럭시 북");
    }
}

/** ConcreteBuilder */
class AppleLaptopBuilder extends LaptopBuilder {
    public void manufacturer() {
    	laptop.setManufacturer("애플");
    }
    public void name() {
    	laptop.setName("맥북");
    }
}

/** Director */
class Assembly {
    private LaptopBuilder laptopBuilder;
    
    public void setLaptopBuilder(LaptopBuilder laptopBuilder) {
	    this.laptopBuilder = laptopBuilder;
    }
    public Laptop getLaptop() {
	    return laptopBuilder.getLaptop();
    }
    public void constructLaptop() {
    	laptopBuilder.createLaptop();
        laptopBuilder.manufacturer();
        laptopBuilder.name();
    }
}

/** Example */
public class BuilderExample {
    public static void main(String[] args) {
        Assembly assembly = new Assembly();
        LaptopBuilder samsung = new SamsungLaptopBuilder();
        LaptopBuilder apple = new SamsungLaptopBuilder();
        
        assembly.setLaptopBuilder(apple);
        assembly.constructLaptop();
        
        Laptop laptop = assembly.getLaptop();
    }
}

예제 코드를 보면, 우선 Director를 생성합니다. 그리고, Product를 어떻게 생성할지에 대한 Concrete Builder를 정합니다. 그런 다음에는 선택한 Concrete Builder를 Director에 넘겨 Product를 생성하게 됩니다. 

 

이런 방식으로 객체를 생성하면 Concrete Builder를 지정하는 부분을 제외하면 객체 생성 절차가 완전히 같게 됩니다. 또한 자신이 원하는 Concrete Builder를 생성하여 적용하면 되므로 표현 결과도 다르게 할 수 있습니다.

하지만, 이 패턴을 적용했을 때의 단점 또한 있습니다. Builder 추상 클래스가 변하면 이는 모든 구현 클래스에 영향을 미치게 됩니다. 또한 객체 하나를 생성할 때에도 Director, Builder 등의 클래스를 함께 생성해야 해서 비용이 많이 들어갈 수 있습니다.

 

 

 

 

이펙티브 자바 빌더 패턴

이펙티브 자바에서 소개하는 빌더 패턴은 생성자, 자바 빈즈 패턴의 단점을 보완하며 객체를 유연하게 생성할 때 사용할 수 있습니다.

이펙티브 자바의 빌더패턴을 사용하는 대표적인 예로써는 Lombok 라이브러리의 @Builder가 이펙티브 자바의 빌더 패턴을 사용합니다.

이 빌더 패턴을 사용한다면 생성자를 하나만 사용해 객체를 생성할 수 있으며, Setter를 사용하지 않고도 원하는 매개변수의 값을 지정할 수 있습니다.

 이도 역시 코드로 알아봅시다.

 

 

 

 

이펙티브 자바 빌더 패턴 예제

public class Laptop {
    private String manufacturer;
    private String name;
    
    public LaptopBuilder builder()
        return new LaptopBuilder();
    }
    
    public Laptop(String manufacturer, String name) {
    	this.manufacturer = manufacturer;
        this.name = name;
    }
    
    public static class LaptopBuilder {
        private String manufacturer;
        private String name;
        
        LaptopBuilder() {}
        
        public LaptopBuilder manufacturer(String manufacturer) {
            this.manufacturer = manufacturer;
            return this;
        }
        public LaptopBuilder name(String name) {
            this.name = name;
            return this;
        }
        
        public Laptop build() {
            return new Laptop(this.manufacturer, this.name);
        }
    }
}

1. 구현할 클래스와 같은 필드를 가지고 있는 빌더 클래스를 만듭니다.

2. 빌더 클래스는  Setter를 만듭니다. 하지만 이 Setter들은 자기 자신(this)을 반환합니다.

3. 마지막으로 빌더 클래스의 변수를 넣은 구현할 클래스를 반환하는 build() 함수를 작성합니다. 이때 빌더 클래스의 모든 필드를 생성자로 넘겨야 하므로 구현할 클래스는 모든 필드를 받는 생성자가 있어야 합니다.

따라서 Lombok의 @Builder를 사용한다면 모든 필드를 받는 생성자를 만들어 주거나 @AllArgsConstructor를 사용해야 합니다.

 

사용은 다음같이 사용합니다.

public class BuilderExample {
    public static void main(String[] args) {
        Laptop laptop = new LaptopBuilder()
            .manufacturer("애플")
            .name("맥북")
            .builder();
            
        // 또는
        Laptop notebook = laptop.builder()
            .manufacturer("애플")
            .name("맥북")
            .builder();
    }
}

 

 

 

🤯사담

개인적으로 스프링을 하면서 빌더 패턴을 많이 사용했습니다. 하지만 항상 Lombok의 Builder를 통해서 빌더 패턴을 사용해서 어떻게 만들어져 있는지 몰랐습니다. 그래서 처음 공부할 때는 빌드해서 나온 .class 파일을 보고 배웠습니다. 또한 내가 사용하는 패턴이 GoF의 디자인 패턴인 줄 알고 있었습니다. 그렇게 공부하며 한 번쯤은 빌더 패턴을 블로그에서 다뤄보고 싶었습니다. 

하지만 블로그 포스팅을 하면서 빌더 패턴이 크게 2가지로 나뉜다는 것을 알게 되었고, 내가 쓰던 빌더 패턴은 이펙티브 자바의 빌더 패턴이었다는 것도 알게 되었습니다. 처음에는 간단하게 쓸려고 했는데 2개의 빌더 패턴 모두 정리하다 보니 포스팅이 생각보다 길어져서 힘들기도 했습니다.

 

 

 

참고.

https://ko.wikipedia.org/wiki/%EB%B9%8C%EB%8D%94_%ED%8C%A8%ED%84%B4

Comments