이숭간 공부기록

[JAVA] 빌더패턴 (Builder Pattern) , @Builder 본문

공부공부/Java

[JAVA] 빌더패턴 (Builder Pattern) , @Builder

이숭간 2021. 8. 8. 14:46
728x90

entity나 Dto객체에 값을 넣어줄때 롬복의 빌더 애노테이션(@Builder)을 종종 사용하곤 하는데 완벽히 이해를 하지 못한것같아 정리해보았다!

빌더패턴이란?

디자인패턴중 하나로, 생성과 표현의 분리(?)란다. 

쉽게 말해 생성자에서 인자가 많을때 고려해볼수있는것이 빌더패턴이다.

 

빌더패턴 이전에 먼저 "점층적 생성자패턴"에 대해 알아보자

 

0. 점증적 생성자패턴

클래스를 설계하다보면, 필수로 받야할 인자들이 있고 선택적으로 받야할 인자들이 있다.

이때 필수적으로 값이 있어야할 멤버변수를 위해 생성자에 매개변수를 넣는다.

또한 선택적 인자를 받기위해 추가적인 생성자를 만든다.

 

이러한 상황을 Effectivce Java 2/E에서 점층적 생성자 패턴이라고 한다.

이런 설계에서의 단점이 있는데

  1. 인자들이 많아질수록 생성자가 많아진다.
  2. 매개변수의 정보를 설명할 수 없으므로 어떤 객체에 어떤 인자가 들어갔는지 알기어렵다.
public class PersonInfo{

  private String name;         //필수적으로 받야할 정보

  private int age;                //선택적으로 받아도 되는 정보

  private int phonNumber;   //선택적으로 받아도 되는 정보

  public PersonInfo(String name){ // 필수니까 강제하기 위해 생성자에 매개변수 추가

     this.name = name;

  }

  public PersonInfo(String name, int age){

    this.name = name;

    this.age = age;

  }

  public PersonInfo(String name, int age, int phonNumber){

    this.name = name;

    this.age = age;

    this.phonNumber = phonNumber;

  }
}
PersonInfo personInfo = new PersonInfo("Mommoo", 12, 119);
// 매개변수의 어떤 위치에 어떤타입과 값을 넣어줘야하는지 개발자가 알고있어야한다.

 

이러한 단점을 보완하기 위해 "자바 빈즈 패턴"이 나오게되었다.

자바 빈즈 패턴

자바 빈즈패턴을 사용하면 생성자의 단점으로 꼽혔던 가독성이 어느정도 해결된다.

다만, 코드량이 늘어나는 단점이 존재하고, 가장 문제가 되는점은 객체일관성이 깨진다는 것이다.

 

객체일관성이 깨진다는것은, 한번 객체를 생성할때 그 객체가 변할 여지가 있다는 것이다.

코드를 보면 객체를 생성하고 그뒤에 값을 떡칠한다.

public class PersonInfo{

  private String name;         //필수적으로 받야할 정보

  private int age;                //선택적으로 받아도 되는 정보

  private int phonNumber;   //선택적으로 받아도 되는 정보

  public PersonInfo(){

  }

  public void setName(String name){

    this.name = name;

  }

  public void setAge(int age){

    this.age = age;

  }

  public void setPhonNumber(int phonNumber){

    this.phonNumber = phonNumber;

  }
}
PersonInfo personInfo = new PersonInfo( ); // 선 객체 생성 후 값 부여

personInfo.setName("Mommoo");         

personInfo.setAge(12);                    

personInfo.setPhonNumber(119);

 

빌더 패턴 

둘의 단점을 모두 보완해서 나타난것이 바로 빌더패턴이다.

정보들은 자바빈즈패턴처럼 받되, 데이터 일관성을 위해 정보들을 다 받은 후에 객체를 생성한다.

 

빌더패턴을 적용하면 다음과 같은 장점이 있다.

  • 불필요한 생성자의 제거
  • 데이터의 순서에 상관없이 객체생성 가능
  • 명시적 선언으로 이해하기가 쉽고
  • 각 인자가 어떤 의미인지 알기 쉽다.
  • setter메서드가 없으므로 변경 불가능한 객체를 만들수있다.
  • 한번에 객체를 생성하므로 객체일관성이 깨지지 않는다.
  • build()함수가 null인지 체크해주므로 검증이 가능한다.
  • 안그러면 set하지않은 객체에대해 get을 하게되는경우 nullPointerExcetpion발생 등등의 문제

빌더패턴은 다음과 같이 만들어진다.

  • A클래스 내부에 빌더클래스를 생성한다.
  • 멤버변수별 메서드를 작성하는데, 각 메소드는 변수에 값을 set하고 빌더객체를 리턴한다.
  • build()메서드는 필수 멤버변수의 null체크를 하고 지금까지 set된 builder를 바탕으로 A클래스의 생성자를 호출하고 인스턴스를 리턴한다.
public class PersonInfo {

    private String name;         //필수적으로 받야할 정보

    private int age;             //선택적으로 받아도 되는 정보

    private int phonNumber;      //선택적으로 받아도 되는 정보

    private PersonInfo() {

    }

    public static class Builder {

        private String name;

        private int age;

        private int phonNumber;


        public Builder(String name) { // 필수변수는 생성자로 값을 넣는다.

            this.name = name;

        }

        // 멤버변수별 메소드 - 빌더클래스의 필드값을 set하고 빌더객체를 리턴한다.

        public Builder setAge(int age) {

            this.age = age;

            return this;

        }

        public Builder setPhonNumber(int phonNumber) {

            this.phonNumber = phonNumber;

            return this;

        }

	// 빌더메소드
        public PersonInfo build() {

            PersonInfo personInfo = new PersonInfo();

            personInfo.name = name;

            personInfo.age = age;

            personInfo.phonNumber = phonNumber;
            
            return personInfo;

        }
    }

위와같이 코드를 작성하면 다음과 같이 객체를 생성할 수 있다.

 

setter에 리턴자료형을 Builder객체로 지정함으로써, 메서드체이닝기법을 적용하고 정보를 다 넣은경우 build()메서드로 객체를 생성한다.

build( ) 메서드를 쓴 이후 에는 PersonInfo 클래스의 멤버변수를 변경 할 수 있는 방법은 리플렉션 기법(동적시점에 변경) 빼곤 존재하지 않는다.

 

따라서, 데이터 일관성, 객체불변성 등을 만족시킨다. 또한 코드 가독성 역시 올라간다.

PersonInfo personinfo = new PersonInfo
    .Builder("SeungJin")    		// 필수값 입력 ( 빌더클래스 생성자로 빌더객체 생성)
    .setAge(25)  			// 값 set
    .setPhoneNumber(1234)
    .build() 				// build() 가 객체를 생성해 돌려준다.

 

Lombok @Builder

위에서 만든 빌더클래스를 직접 만들지 않아도 롬복플러그인이 지원해주는 어노테이션 하나로 클래스를 생성할 수 있다.

 

클래스 또는 생성자 위에 @Builder어노테이션을 붙여주면 빌더패턴 코드가 빌드된다.

생성자 상단에 선언시 생성자에 포함된 필드만 빌더에 포함!

@Builder
public class Person {
    private final String name;
    private final int age;
    private final int phone;
}

객체를 다음과 같이 생성할 수 있게된다.

Person person = Person.builder() // 빌더어노테이션으로 생성된 빌더클래스 생성자
    .name("seungjin")
    .age(25)
    .phone(1234)
    .build();

 

 

참조 : 

johngrib.github.io/wiki/builder-pattern/

mommoo.tistory.com/54