참고 블로그

 

자바(Java)에서 객체를 생성할 때 사용하는 패턴이 여러 가지가 있습니다. 그 중 가장 대표적안 생성자 패턴(constructor pattern)은 지금 채워야 할 필드가 무엇인지 명확히 지정할 수 없습니다. 하지만 빌더 패턴(builder pattern)을 사용하면 어느 필드에 어떤 값을 채워야 할지 명확하게 지정할 수 있습니다.

 

일반적인 생성자 패턴의 예시는 다음과 같습니다.

package com.example.awsboard.web.dto;

public class Car {
    
    private String id, name;
    
    public Car(String id, String name) {
        this.id = id;
        this.name = name;
    }

    // Getter
    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

}
String id = "1";
String name = "Charles";

Car car1 = new Car(id, name)
Car car2 = new Car(name, car) // ??

일반적인 생성자 패턴은 위와 같이 같은 스트링 타입 변수의 위치가 뒤바뀐 채로 생성되어도 문제점을 찾지 못하고 넘어갈 수도 있다는 점을 알 수 있습니다.

 

이것을 빌더 패턴으로 바꾸면 다음과 같습니다.

package com.example.awsboard.web.dto;

public class Car {

    // final 키워드를 써서 생성자를 통한 입력을 강요함
    private final String id, name;

    // 클래스 안에 스태틱 형태의 내부 클래스(inner class) 생성
    protected static class Builder {
        private String id;
        private String name;

        // id 입력값 받음: 리턴 타입을 Builder 타입으로 한 다음 this를 리턴
        public Builder id(String value) {
            id = value;
            return this;
        }

        // name 입력값 받음: 리턴 타입을 Builder 타입으로 한 다음 this를 리턴
        public Builder name(String value) {
            name = value;
            return this;
        }

        // 마지막에 build() 메소드를 실행하면 this가 리턴되도록 함
        public Car build() {
            return new Car(this);
        }
    }

    // 생성자를 private로 함
    // 외부에서는 접근할 수 없고 위의 Builder 클래스에서는 사용 가능
    private Car(Builder builder) {
        id = builder.id;
        name = builder.name;
    }
    
    // 빌더 소환: 외부에서 Car.builder() 형태로 접근 가능하게 스태틱 메소드로 
    public static Builder builder() {
        return new Builder();
    }

    // Getter
    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

}
String id = "1";
String name = "Charles";

Car car1 = Car.builder()
        .id(id)
        .name(name)
        .build();

 

위의 빌더 패턴으로 만든 Car에 대한 단위 테스트는 다음과 같습니다.

package com.example.awsboard.web.dto;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class CarTest {

    @Test
    public void 빌더_테스트_1() {
        String id = "1";
        String name = "Charles";

        Car car1 = Car.builder()
                .id(id)
                .name(name)
                .build();

        assertThat(car1.getId()).isEqualTo(id);
        assertThat(car1.getName()).isEqualTo(name);

    }
}

 

롬복(Lombok)으로 빌더 패턴 적용

참고: Spring Boot: Gradle 버전 5 이상에서 롬복 설치 + 단위 테스트

package com.example.awsboard.web.dto;

import lombok.Builder;
import lombok.Getter;

@Getter // Getter 생성
public class LombokCar {
    private String id;
    private String name;

    @Builder // 생성자를 만든 후 그 위에 @Build 어노테이션 적용
    public LombokCar(String id, String name) {
        this.id = id;
        this.name = name;
    }
}

 

단위 테스트는 다음과 같습니다.

 

package com.example.awsboard.web.dto;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class LombokCarTest {

    @Test
    public void 빌더_테스트_2() {
        String id = "2";
        String name = "James";

        LombokCar car2 = LombokCar.builder()
                .id(id)
                .name(name)
                .build();

        assertThat(car2.getId()).isEqualTo(id);
        assertThat(car2.getName()).isEqualTo(name);

    }
}