Lombok 라이브러리의 @Builder 어노테이션은 정말 많이 사용하는 어노테이션이다. 항상 생성자 자체를 사용하기 보단 @Builder을 사용해서 생성자를 만들곤 했다. 하지만 사용하면서도 Builder의 패턴이나 사용이유에 대해 깊게 생각하지 못하고 넘어갔던 것 같아 정리하고자 한다. 이번 기회에 공식문서 읽는 습관도 들여야겠다. 🌱
@Builder와 일반 객체 생성의 차이점
- 객체를 생성하는 대표적인 방법은 “생성자 패턴”, “자바빈 패턴”, “빌더 패턴”등이 있다.
- 근데 왜 우리는 “빌더패턴”을 유용하게 사용하는걸까?
Telescoping Constructor Pattern (점층적 생성자 패턴)
- 각 생성자를 오버로딩해서 만드는 기초적인 생성자 패턴
public class Member { private String name; private String id; private String password; public Member(String name) { this.name = name; } public Member(String name, String id) { this.name= name; this.id = id; } public Member(String name, String id, String password) { this.name = name; this.id = id; this.password = password; } }
- 장점 : 가장 기본적인 패턴으로 쉽게 작성 가능
- 단점 : 인자가 많아질수록 생성자가 많아짐 + 매개변수의 정보 설명 불가능
⇒ 코드 수정 또는 필드 추가가 복잡해진다!
Java Beans Pattern (자바 빈 패턴)
- getter/setter을 이용해서 객체를 생성할 때, 필드를 주입하는 방식
public class Member { private String name; private String id; private String password; public Member() { } public void setName(String name) { this.name = name; } public void setId(String id) { this.id = id; } public void setPassword(String password) { this.password = password; } }
- 장점 : 이전 생성자패턴보단 가독성이 좋아진다.
- 단점 : getter/setter로 인해 코드량이 늘어남 + setter메소드는 값이 계속 변경될 수 있기에 객체일관성 깨짐
⇒ 한 번 객체를 생성할 때, 그 객체가 변할 여지가 있다.
정리 | 기존 생성자 패턴 ↔ 빌더 패턴
1️⃣ 생성자는 파라미터가 많을 경우 가독성이 좋지 않다.
- City 클래스는 생성자 파라미터가 8개나 된다.
- 여기에 파라미터가 더 추가된다면? 각 값들이 어떤 것을 의미하는지 파악하기 어렵다.
City city = new City("지역1", "도로A", "20만", null, null);
- 이를 Builder 패턴으로 적용시켜보자.
City city = City.builder() .name() .roadAddress() .population() .latitude() .longitude() .build();
- Builder 패턴을 사용시, 각 값들이 어떤것을 의미하는지 파악하기 쉬워진다.
- 즉, 가독성이 매우 편리해지고 같은 타입의 변수를 서로 바꿔 넣는 것을 방지할 수 있다.
2️⃣ 불필요한 매개변수의 값까지 지정해줘야 한다.
- 생성자 호출을 위해 설정하길 원하지 않는 매개변수의 값까지 null로 지정해줘서 호출해야한다.
City city = new City("지역1", "도로A", "20만", null, null);
3️⃣ 매개변수의 순서가 바뀌면 에러가 난다.
- 생성자는 매개변수의 순서가 곧 생성자의 필드 순서와 같다.
- 그러므로, 매개변수의 순서가 달라져서 type Error가 날 수있다.
- 또한, 컴파일러가 해당 에러를 잡지 못하면 런타임에러로 이어질 수 있다.
@Builder란?
- @Builder란, Lombok에서 제공하는 어노테이션으로, 생성자 인자를 메서드 체인을 통해 명시적으로 대입하여 생성자를 호출할 수 있게 클래스를 생성해준다.
- IDE에서 Builder를 사용하게 된다면 자동완성 기능이 있어 생성자 작성보다 오기입 확률을 낮출 수 있다.
- @Builder는 생성자, 메서드 또는 클래스 레벨에서 사용 가능하다.
Builder Pattern (빌더패턴)
- @Builder 어노테이션을 사용해서 생성자를 편리하게 만들도록 하는 패턴
- 디자인 패턴 중 하나로, 생성과 표현의 분리를 의미한다.
- 즉, 복합 객체의 생성 과정과 표현 방법을 분리해 동일한 생성 절차에서 다른 표현 결과를 만들 수 있는 패턴이다.
- 클래스를 설계하다보면, 필수로 받아야하는 인자들이 있고, 선택적으로 받아야하는 인자들이 있다.
- 이때 생성자에서 인자들이 많을 때, 빌더패턴을 주로 사용한다.
클래스 레벨
- 클래스 레벨에서 @Builder를 붙이면, 모든 요소를 받는 package-private 생성자가 자동으로 생성된다.
- setter 메서드를 만들 필요 없이 모든 필드가 포함된 생성자가 생성된다.
- 생성한 객체를 불변으로 만들 수 있다. 즉, 필드값 설정 이후 변경 불가하다.
@Getter
@Builder
public class AccommodationResponseDTO {
private Long itineraryId;
private String itineraryName;
private String accommodationName;
private String accommodationRoadAddressName;
private String checkIn;
private String checkOut;
}
생성자 레벨
- 별도의 생성자를 직접 만들고, 빌더 패턴을 적용한다.
- private 생성자를 가지는 클래스Builder라는 이름의 내부 빌더 클래스를 생성하여 빌더 패턴을 구현한다.
☑️ 클래스 레벨 ↔ 생성자 레벨
- 클래스 레벨은 가능한 모든 필드에 대하여 빌더 메서드를 생성
- 생성자 레벨에서는 생성자의 파라미터 필드에 대해서만 빌더 메서드를 생성
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Trip {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private LocalDate startDate;
private LocalDate endDate;
@Builder
public Trip(Long id, String name, LocalDate startDate, LocalDate endDate) {
this.id = id;
this.name = name;
this.startDate = startDate;
this.endDate = endDate;
}
메서드 레벨
- 메서드레벨에서는 @Builder가 자주 사용되지 않는다.
- 이는 객체 초기화 및 불변성을 목표로하는 빌더패턴의 목적에 벗어나기 때문이다.
public class Car {
private String make;
private String model;
private int year;
private int price;
@Builder
public static Car createCar(String make, String model, int year, int price) {
return new Car(make, model, year, price);
}
private Car(String make, String model, int year, int price) {
this.make = make;
this.model = model;
this.year = year;
this.price = price;
}
}
🔗 참고 url
https://velog.io/@park2348190/Lombok-Builder의-동작-원리
https://charliezip.tistory.com/17
Uploaded by N2T