본문 바로가기
Java/Java

Builder Pattern과 lombok @Builder

by 개발하는 호빗 2022. 4. 10.

Builder는 주로 lombok의 @Builder 어노테이션을 이용해 쉽게 사용해서 직접 구현해본 적은 한 번뿐이었다.

이펙티브 자바를 읽다 Builder 패턴이 나온 김에 예제를 따라서 직접 구현해보기로 했다.

예제 클래스명은 Coffee로 바꿔서 구현했다.

구현

public class Coffee {

    private String bean;
    private boolean water;
    private boolean syrup;

    public Coffee(Builder builder) {
        bean = builder.bean;
        water = builder.water;
        syrup = builder.syrup;
    }

    public static class Builder {
        //필수 매개변수
        private String bean;
        private boolean water;

        //선택 매개변수
        private boolean syrup;

        public Builder(String bean, boolean water) {
            this.bean = bean;
            this.water = water;
        }

        public Builder syrup(boolean val) {
            this.syrup = val;
            return this;
        }

        public Coffee build() {
            return new Coffee(this);
        }
    }
}

 

public class MakeCoffee {
	…
        //@Builder 사용할 땐 new FooClass 한 적이 없었는데?
        new Coffee.Builder("Brazil", true)//Coffee 클래스의 이너 클래스 Builder를 생성!
                .syrup(true)
                .build();
}

Builder의 setter 메서드와 메서드 연쇄 호출

Builder 클래스엔 필드별로 프로퍼티명을 이름으로 하는 setter 메서드를 두는데,

이때 setter 메서드는 자기 자신인 Builder 클래스를 반환함으로써 아래와 같은 메서드 연쇄 호출을 가능하게 한다.

new Coffee.Builder().bean(”Brazil”).water(true).syrup(true).build();

 

 

그런데 이렇게 만들고 나니 lombok의 @Builder 어노테이션을 사용할 땐 new Coffee.Builder() 형태로 사용한 적이 없었다는 게 의문이었다.

아래와 같이 Coffee.builder()를 호출한 후 field를 설정할 수 있었는데 차이가 뭘까?

@Builder
public class Coffee {
    private String bean;
    private boolean water;
    private boolean syrup;
}

 

public class MakeCoffee {
	…
       //lombok @Builder
        Coffee.builder()
                .bean("Brazil")
                .water(true)
                .build();
}

차이만 보자면 단순했다. Coffee 클래스 안에 builder() 메서드를 두고, 내부 클래스인 Builder클래스 인스턴스를 생성하는 코드를 감춘 것이었다.

public class Coffee {
    private String bean;
    private boolean water;
    private boolean syrup;

    public static Coffee.Builder builder() {
        return new Coffee.Builder();
    }

    public static Coffee.Builder builder(String bean, boolean water) {
        return new Coffee.Builder(bean, water);
    }

    public Coffee(String bean, boolean water, boolean syrup) {
        this.bean = bean;
        this.water = water;
        this.syrup = syrup;
    }

    public static class Builder {
        //필수 매개변수
        private String bean;
        private boolean water;

        //선택 매개변수
        private boolean syrup;

        public Builder() {

        }

        public Builder(String bean, boolean water) {
            this.bean = bean;
            this.water = water;
        }

        public Builder bean(String val) {
            this.bean = bean;
            return this;
        }

        public Builder water(boolean water) {
            this.water = water;
            return this;
        }

        public Builder syrup(boolean val) {
            this.syrup = val;
            return this;
        }

        public Coffee build() {
            return new Coffee(bean, water, syrup);
        }
    }
}

 

public class MakeCoffee {
	…
       //Builder Pattern에 new FooClass.Builder()를 builder()로 감쌌음.
        //필수 매개변수는 없음
        Coffee.builder()
                .bean("Brazil")
                .water(true)
                .build();

        //필수 매개변수는 있음
        Coffee.builder("Brazil", true)
                .build();
}

구현을 해보니 눈에 띄었던 점이 하나 더 있었는데, Builder 내부 클래스 안에서 Coffee 클래스의 모든 필드를 가진 생성자를 호출하는 부분이었다.

클래스 레벨 @Builder와 AllArgConstructor

@Builder는 기본적으로 메서드, 생성자 레벨에 붙여서 사용하는데

클래스 레벨 어노테이션으로 쓸 경우 모든 필드를 인자로 받는 package-private 생성자가 자동으로 생성되며,

그 생성자에 @Builder를 붙여서 사용하는 것과 같은 동작을 한다.

package-private 생성자는 직접 생성자를 작성해두지 않았거나, @XArgsConstructor를 사용하지 않았을 경우에만 생성된다.

​직접 구현으로 알게 된 것들

lombok의 @Builder 어노테이션

  • 클래스의 필드를 똑같이 가진 Builder 내부 클래스 생성
  • Builder 내부 클래스의 생성자를 호출하는 builder() 메서드 생성
  • Builder 내부 클래스 안에 각 필드 별로 프로퍼티 명을 이름으로 하는 setter 메서드 생성. 이 setter 메서드는 Builder 자신을 반환하며 이를 통해 메서드 연쇄 호출을 가능하게 한다.
  • Builder 내부 클래스 안에 @Builder를 적용한 클래스의 모든 인자 생성자를 반환하는 build() 메서드 생성

​참고  
[1] https://projectlombok.org/features/Builder
[2] https://velog.io/@park2348190/Lombok-Builder의-동작-원리

'Java > Java' 카테고리의 다른 글

Java 참조  (0) 2023.10.08
Collections.emptyList()와 Collections.EMPTY_LIST의 차이  (0) 2023.04.27
Java 기초 :: JVM과 컴파일  (0) 2022.05.09
Java :: Map loop 사용하기  (0) 2022.03.17