Builder 빌더
객체 생성시 생성자를 좀 더 가독성 있게 사용할 수 있는 빌더패턴에 대해 알아봅시다.
빌더 패턴 (Builder Pattern) 정리
1. 개념
빌더 패턴은 객체 생성 과정에서 생성자를 사용하지 않고, 단계적으로 객체를 구성하여 마지막에 완성된 객체를 반환하는 방식입니다. 이 패턴은 가독성을 높이고, 객체를 유연하고 안정적으로 생성할 수 있는 방법을 제공합니다. 특히, 파라미터가 많거나 다양한 조합의 파라미터를 사용할 때 매우 유용합니다.
빌더 패턴은 다음과 같은 특징을 가지고 있습니다
-
유연한 객체 생성: 선택적인 필드나 필수 필드를 구분하여 객체를 쉽게 만들 수 있습니다.
-
가독성 향상: 메서드 체이닝 방식으로 필드 값을 설정할 수 있어 코드의 가독성이 좋아집니다.
2. 탄생 배경
빌더 패턴은 복잡한 객체 생성 과정을 단순화하고 가독성을 높이기 위해 등장한 디자인 패턴입니다. 특히, 다음과 같은 문제를 해결하기 위해 고안되었습니다
-
복잡한 객체 생성: 객체가 생성될 때 많은 파라미터가 필요하거나, 객체의 필수 및 선택적인 필드가 많은 경우 생성자를 통해 객체를 생성하는 것은 코드의 가독성을 떨어뜨리고 유지보수를 어렵게 만듭니다.
-
가독성 저하: 생성자에 많은 인자가 포함되면, 호출 시 어떤 값이 어떤 파라미터에 해당하는지 명확하지 않아 코드의 가독성이 떨어집니다.
-
점층적 생성자 패턴의 한계: 파라미터가 늘어날수록, 점층적 생성자 패턴 (Telescoping Constructor Pattern)을 사용할 때 여러 개의 생성자를 정의해야 하고, 이는 코드 복잡성을 증가시킵니다.
2-1 점층적 생성자 패턴 (Telescoping Constructor Pattern) with Lombok
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static void main(String[] args) {
// 모든 필드를 지정하는 생성자 사용
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
System.out.println(cocaCola);
}
// 위의 객체를 생성할 때 각 파라미터가 어떤 속성에 해당하는지 알기 어려움.
@Override
public String toString() {
return "NutritionFacts [servingSize=" + servingSize + ", servings=" + servings +
", calories=" + calories + ", fat=" + fat +
", sodium=" + sodium + ", carbohydrate=" + carbohydrate + "]";
}
}
Lombok의 @AllArgsConstructor 어노테이션은 모든 필드를 초기화하는 생성자를 자동으로 생성해줍니다. 하지만 점층적 생성자 패턴의 경우, 필요에 따라 추가적인 생성자를 수동으로 작성해야 하는 불편함이 여전히 존재합니다.
2-2 빌더 패턴 (Builder Pattern) with Lombok
import lombok.Builder;
import lombok.ToString;
@Builder
@ToString
public class NutritionFacts {
private final int servingSize; // 필수
private final int servings; // 필수
private final int calories; // 선택적
private final int fat; // 선택적
private final int sodium; // 선택적
private final int carbohydrate; // 선택적
public static void main(String[] args) {
// 빌더 패턴으로 객체 생성
NutritionFacts cocaCola = NutritionFacts.builder()
.servingSize(240)
.servings(8)
.calories(100)
.sodium(35)
.carbohydrate(27)
.build();
// 위의 객체 생성시 파라미터와 속성을 함께 작성하여 어떤 파라미터인지 알기 쉽게 만듦
System.out.println(cocaCola);
}
}
Lombok의 @Builder 어노테이션을 사용하면 자동으로 빌더 패턴을 구현할 수 있습니다. 필드들을 체이닝 방식으로 설정할 수 있으며, 필요하지 않은 필드는 생략할 수 있습니다.
3. lombok 의 @Builder 와 @ToString
3-1 (lombok 사용 x)
public class Person {
private String firstName;
private String lastName;
private int age;
private String address;
// Person 클래스의 private 생성자
private Person(PersonBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.address = builder.address;
}
// PersonBuilder 내부 클래스
public static class PersonBuilder {
private String firstName;
private String lastName;
private int age;
private String address;
public PersonBuilder firstName(String firstName) {
this.firstName = firstName;
return this;
}
public PersonBuilder lastName(String lastName) {
this.lastName = lastName;
return this;
}
public PersonBuilder age(int age) {
this.age = age;
return this;
}
public PersonBuilder address(String address) {
this.address = address;
return this;
}
public Person build() {
return new Person(this);
}
}
// 생성시킨 객체를 더 편하게 보기위해서(테스트를 용이하게 하려고 작성)
// lombok 사용시 @ToString 으로 대체능
@Override
public String toString() {
return "Person [firstName=" + firstName + ", lastName=" + lastName + ", age=" + age + ", address=" + address + "]";
}
public static void main(String[] args) {
// 빌더 패턴으로 객체 생성
Person person = new Person.PersonBuilder()
.firstName("John")
.lastName("Doe")
.age(30)
.address("123 Main St")
.build();
System.out.println(person);
}
}
3-2 (lombok 사용)
import lombok.Builder;
import lombok.ToString;
@Builder
@ToString
public class Person {
private String firstName;
private String lastName;
private int age;
private String address;
public static void main(String[] args) {
// @Builder 어노테이션으로 객체 생성
Person person = Person.builder()
.firstName("John")
.lastName("Doe")
.age(30)
.address("123 Main St")
.build();
System.out.println(person);
}
}