1. 개요

이 사용방법(예제)에서는 FreeBuilder 라이브러리를 사용하여 Java에서 빌더 클래스를 생성합니다.

2. 빌더 디자인 패턴

빌더는 객체 지향 언어에서 가장 널리 사용되는 생성 디자인 패턴 중 하나입니다. 복잡한 도메인 개체의 인스턴스화를 추상화하고 인스턴스 생성을 위한 유창한 API를 제공 합니다 . 따라서 간결한 도메인 계층을 유지하는 데 도움이 됩니다.

유용함에도 불구하고 빌더는 일반적으로 특히 Java에서 구현하기 복잡합니다. 더 간단한 값 개체에도 많은 상용구 코드가 필요합니다.

3. Java에서 빌더 구현

FreeBuilder를 진행하기 전에 Employee  클래스 에 대한 상용구 빌더를 구현해 보겠습니다 .

public class Employee {

    private final String name;
    private final int age;
    private final String department;

    private Employee(String name, int age, String department) {
        this.name = name;
        this.age = age;
        this.department = department;
    }
}

그리고 내부  빌더  클래스:

public static class Builder {

    private String name;
    private int age;
    private String department;

    public Builder setName(String name) {
        this.name = name;
        return this;
    }

    public Builder setAge(int age) {
        this.age = age;
        return this;
    }

    public Builder setDepartment(String department) {
        this.department = department;
        return this;
    }

    public Employee build() {
        return new Employee(name, age, department);
    }
}

따라서 이제 Employee  개체를 인스턴스화하기 위해 빌더를 사용할 수 있습니다 .

Employee.Builder emplBuilder = new Employee.Builder();

Employee employee = emplBuilder
  .setName("baeldung")
  .setAge(12)
  .setDepartment("Builder Pattern")
  .build();

위와 같이 빌더 클래스를 구현하기 위해서는 많은 상용구 코드가 필요합니다.

이후 섹션에서는 FreeBuilder가 이 구현을 즉시 단순화하는 방법을 살펴보겠습니다.

4. 메이븐 의존성

FreeBuilder 라이브러리를 추가하기 위해 pom.xmlFreeBuilder Maven 의존성을 추가합니다 .

<dependency>
    <groupId>org.inferred</groupId>
    <artifactId>freebuilder</artifactId>
    <version>2.4.1</version>
</dependency>

5. FreeBuilder 어노테이션

5.1. 빌더 생성

FreeBuilder는 개발자가 빌더 클래스를 구현하는 동안 상용구 코드를 피할 수 있도록 도와주는 오픈 소스 라이브러리입니다. 빌더 패턴의 구체적인 구현을 생성하기 위해 Java의 어노테이션 처리를 사용합니다.

@ FreeBuilder 로 이전 섹션의 Employee  클래스 에 어노테이션을 달고  자동으로 빌더 클래스를 생성하는 방법을 확인합니다 . 

@FreeBuilder
public interface Employee {
 
    String name();
    int age();
    String department();
    
    class Builder extends Employee_Builder {
    }
}

Employee는  이제  POJO 클래스가 아닌 인터페이스  라는 점을 지적하는 것이 중요합니다  . 또한  Employee  개체의 모든 특성을 메서드로 포함합니다.

이 빌더를 계속 사용하기 전에 컴파일 문제를 방지하도록 IDE를 구성해야 합니다. FreeBuilder는  컴파일 중에 Employee_Builder  클래스를 자동으로 생성하므로  IDE 일반적으로 8번 줄에서 ClassNotFoundException 에 대해 불평합니다 .

이러한 문제를 방지하려면 IntelliJ 또는 Eclipse 에서 어노테이션 처리를 활성화해야 합니다 . 그렇게 하는 동안 FreeBuilder의 어노테이션 프로세서 org.inferred.freebuilder.processor.Processor를 사용합니다  . 또한 이러한 소스 파일을 생성하는 데 사용되는 디렉토리는 Generated Sources Root 로 표시되어야 합니다 .

또는 mvn install을 실행하여 프로젝트를 빌드하고 필요한 빌더 클래스를 생성 할 수도 있습니다 .

마지막으로 프로젝트를 컴파일했으며 이제  Employee.Builder  클래스를 사용할 수 있습니다.

Employee.Builder builder = new Employee.Builder();
 
Employee employee = builder.name("baeldung")
  .age(10)
  .department("Builder Pattern")
  .build();

대체로 이 클래스와 이전에 본 빌더 클래스 사이에는 두 가지 주요 차이점이 있습니다. 먼저 Employee  클래스 의 모든 특성에 대한 값을 설정해야 합니다 . 그렇지 않으면 IllegalStateException 이 발생합니다 .

이후 섹션에서 FreeBuilder가 선택적 속성을 처리하는 방법을 살펴보겠습니다.

둘째, Employee.Builder  의 메소드 이름은  JavaBean 명명 규칙을 따르지 않습니다. 다음 섹션에서 이에 대해 살펴보겠습니다.

5.2. JavaBean 명명 규칙

FreeBuilder가 JavaBean 명명 규칙을 따르도록 강제하려면 Employee  에서 메소드의 이름을 바꾸고  메소드 앞에 get을 붙여야 합니다 .

@FreeBuilder
public interface Employee {
 
    String getName();
    int getAge();
    String getDepartment();

    class Builder extends Employee_Builder {
    }
}

이것은 JavaBean 명명 규칙을 따르는 getter 및 setter를 생성합니다.

Employee employee = builder
  .setName("baeldung")
  .setAge(10)
  .setDepartment("Builder Pattern")
  .build();

5.3. 매퍼 방법

게터 및 세터와 함께 FreeBuilder는 빌더 클래스에 매퍼 메서드도 추가합니다. 이러한 매퍼 메서드는 UnaryOperator를 입력으로 허용 하므로 개발자가 복잡한 필드 값을 계산할 수 있습니다.

Employee 클래스에도 급여 필드가 있다고 가정합니다 .

@FreeBuilder
public interface Employee {
    Optional<Double> getSalaryInUSD();
}

이제 입력으로 제공된 급여의 통화를 변환해야 한다고 가정합니다.

long salaryInEuros = INPUT_SALARY_EUROS;
Employee.Builder builder = new Employee.Builder();

Employee employee = builder
  .setName("baeldung")
  .setAge(10)
  .mapSalaryInUSD(sal -> salaryInEuros * EUROS_TO_USD_RATIO)
  .build();

FreeBuilder는 모든 필드에 대해 이러한 매퍼 메서드를 제공합니다.

6. 기본값 및 제약 조건 검사

6.1. 기본값 설정

지금까지 논의한 Employee.Builder 구현에서는 클라이언트가 모든 필드의 값을 전달할 것으로 예상 합니다 . 실제로  필드가 누락된 경우 IllegalStateException  으로 초기화 프로세스에 실패합니다.

이러한 실패를 방지하기 위해 필드의 기본값을 설정하거나 선택 사항으로 만들 수 있습니다 .

Employee.Builder  생성자 에서 기본값을 설정할 수 있습니다  .

@FreeBuilder
public interface Employee {

    // getter methods

    class Builder extends Employee_Builder {

        public Builder() {
            setDepartment("Builder Pattern");
        }
    }
}

따라서 생성자에서 기본  부서를  설정하기만 하면 됩니다. 이 값은 모든 직원  개체 에 적용됩니다 .

6.2. 구속조건 확인

일반적으로 필드 값에 대한 특정 제약 조건이 있습니다. 예를 들어 유효한 이메일에는 "@"가 포함되어 있거나 직원  의 나이가 범위 내에 있어야 합니다.

이러한 제약 조건으로 인해 입력 값에 대한 유효성 검사가 필요합니다. 그리고 FreeBuilder를 사용하면 setter  메서드를 재정의하기만 하면 이러한 유효성 검사를 추가할 수 있습니다  .

@FreeBuilder
public interface Employee {

    // getter methods

    class Builder extends Employee_Builder {

        @Override
        public Builder setEmail(String email) {
            if (checkValidEmail(email))
                return super.setEmail(email);
            else
                throw new IllegalArgumentException("Invalid email");

        }

        private boolean checkValidEmail(String email) {
            return email.contains("@");
        }
    }
}

7. 선택 값

7.1. 선택적 필드 사용

일부 개체에는 값이 비어 있거나 null일 수 있는 선택적 필드가 포함되어 있습니다. FreeBuilder를 사용하면 Java Optional 유형을 사용하여 이러한 필드를 정의할 수 있습니다 .

@FreeBuilder
public interface Employee {

    String getName();
    int getAge();

    // other getters
    
    Optional<Boolean> getPermanent();

    Optional<String> getDateOfJoining();

    class Builder extends Employee_Builder {
    }
}

이제 선택적  필드 에 대한 값 제공을 건너뛸 수 있습니다 .

Employee employee = builder.setName("baeldung")
  .setAge(10)
  .setPermanent(true)
  .build();

특히 우리는 선택 사항 대신 영구 필드 에 대한 값을 전달했습니다 dateOfJoining  필드 의 값을  설정하지 않았으므로 선택적  필드 의 기본값인  Optional.empty()  가 됩니다  .

7.2. @Nullable 필드 사용

Java에서 null 을 처리하기 위해 Optional을 사용하는 것이 좋지만 FreeBuilder는 이전 버전과의 호환성을 위해 @Nullable을 사용할 수 있습니다 .

@FreeBuilder
public interface Employee {

    String getName();
    int getAge();
    
    // other getter methods

    Optional<Boolean> getPermanent();
    Optional<String> getDateOfJoining();

    @Nullable String getCurrentProject();

    class Builder extends Employee_Builder {
    }
}

선택 사항을 사용하는  것은 어떤 경우에는 잘못된 조언이며 이는 빌더 클래스에 @Nullable  이 선호되는 또 다른 이유입니다 .

8. 컬렉션 및 맵

FreeBuilder는 컬렉션과 맵을 특별히 지원합니다.

@FreeBuilder
public interface Employee {

    String getName();
    int getAge();
    
    // other getter methods

    List<Long> getAccessTokens();
    Map<String, Long> getAssetsSerialIdMapping();


    class Builder extends Employee_Builder {
    }
}

FreeBuilder는 빌더 클래스의 컬렉션에 입력 요소를 추가하는 편리한 메서드를 추가합니다 .

Employee employee = builder.setName("baeldung")
  .setAge(10)
  .addAccessTokens(1221819L)
  .addAccessTokens(1223441L, 134567L)
  .build();

수정할 수 없는 List을 반환하는 빌더 클래스의 getAccessTokens()  메서드 도 있습니다  . 마찬가지로  Map의 경우:

Employee employee = builder.setName("baeldung")
  .setAge(10)
  .addAccessTokens(1221819L)
  .addAccessTokens(1223441L, 134567L)
  .putAssetsSerialIdMapping("Laptop", 12345L)
  .build();

Map  에 대한  getter  메서드  수정 불가능한 맵을 클라이언트 코드로 반환합니다.

9. 중첩 빌더

실제 응용 프로그램의 경우 도메인 엔터티에 대해 많은 값 개체를 중첩 해야 할 수 있습니다 . 그리고 중첩된 객체 자체에 빌더 구현이 필요할 수 있으므로 FreeBuilder는 중첩된 빌드 가능 유형을 허용합니다.

예를 들어 Employee  클래스 에  중첩된 복합 유형 주소가  있다고 가정합니다  .

@FreeBuilder
public interface Address {
 
    String getCity();

    class Builder extends Address_Builder {
    }
}

이제 FreeBuilder는  Address.Builder를 주소  유형 과 함께 입력으로 사용하는 setter  메서드를  생성합니다.

Address.Builder addressBuilder = new Address.Builder();
addressBuilder.setCity(CITY_NAME);

Employee employee = builder.setName("baeldung")
  .setAddress(addressBuilder)
  .build();

특히 FreeBuilder는 Employee 의  기존  Address  개체를 사용자 지정하는 메서드도 추가합니다 .

Employee employee = builder.setName("baeldung")
  .setAddress(addressBuilder)
  .mutateAddress(a -> a.setPinCode(112200))
  .build();

FreeBuilder  유형 과 함께  FreeBuilder는 protos 와 같은 다른 빌더의 중첩도 허용합니다 .

10. 부분 객체 만들기

이전에 논의한 바와 같이 FreeBuilder는 모든 제약 조건 위반(예: 필수 필드 값 누락)에 대해 IllegalStateException  을 발생시킵니다.

이것은 프로덕션 환경에서 바람직 하지만 일반적으로 제약 조건과 무관한 단위 테스트를 복잡하게 만듭니다 .

이러한 제약을 완화하기 위해 FreeBuilder를 사용하면 부분 개체를 만들 수 있습니다.

Employee employee = builder.setName("baeldung")
  .setAge(10)
  .setEmail("abc@xyz.com")
  .buildPartial();

assertNotNull(employee.getEmail());

따라서 Employee 에 대한 모든 필수 필드를 설정하지 않았더라도 email  필드에 유효한 값이 있는지 확인할 수 있습니다 .

11. 커스텀 toString()  메소드

값 개체를 사용하면 종종 사용자 지정 toString()  구현을 추가해야 합니다 . FreeBuilder는 추상  클래스를 통해 이를 허용합니다  .

@FreeBuilder
public abstract class Employee {

    abstract String getName();

    abstract int getAge();

    @Override
    public String toString() {
        return getName() + " (" + getAge() + " years old)";
    }

    public static class Builder extends Employee_Builder{
    }
}

Employee를  인터페이스가 아닌 추상 클래스로 선언  하고 사용자 정의 toString()  구현을 제공했습니다.

12. 다른 빌더 라이브러리와의 비교

이 기사에서 논의한 빌더 구현은 Lombok , Immutables 또는 기타 어노테이션 프로세서 의 구현과 매우 유사합니다 . 그러나 이미 논의한 몇 가지 특징이 있습니다  .

    • 매퍼 방법
    • 중첩된 빌드 가능 유형
    • 부분 객체

13. 결론

이 기사에서는 FreeBuilder 라이브러리를 사용하여 Java에서 빌더 클래스를 생성했습니다. 어노테이션을 사용하여 빌더 클래스의 다양한 사용자 정의를 구현하여 구현에 필요한 상용구 코드를 줄였습니다 .

또한 FreeBuilder가 다른 라이브러리와 어떻게 다른지 살펴보고 이 기사에서 이러한 특성 중 일부를 간략하게 논의했습니다.

모든 코드 예제는 GitHub 에서 사용할 수 있습니다 .

Generic footer banner