1. Builder 패턴과 @Builder란?
Builder 패턴은 복잡한 객체 생성 과정을 단계별로 구성할 수 있도록 도와주는 디자인 패턴으로,
Lombok의 `@Builder`어노테이션은 Builder 패턴을 적용할 수 있도록 도와준다.
복잡한 객체 생성 과정이란?
매개 변수가 많은 생성자 일부 필드가 선택 사항이고, 조건에 따라 값이 달라져야 하는 경우
객체 생성 시 유효성 검증, 기본 값 설정, 복잡한 계산 로직이 필요한 경우
따라서, Builder 패턴을 사용하면,
- 생성자에 전달하는 파라미터의 순서나 누락 문제를 방지하고
- 가독성이 좋고, 코드 유지보수가 쉬우며
- 불필요한 생성자 오버라이딩 줄일 수 있다.
2. Builder 패턴 사용 예시
[ParkingLot 엔티티]
package com.parkez.parkinglot.domain.entity;
import com.parkez.common.entity.BaseDeleteEntity;
import com.parkez.common.entity.BaseEntity;
import com.parkez.parkinglot.domain.enums.ChargeType;
import com.parkez.parkinglot.domain.enums.ParkingLotStatus;
import com.parkez.parkinglot.domain.enums.SourceType;
import com.parkez.parkinglot.dto.request.ParkingLotRequest;
import com.parkez.user.domain.entity.User;
import jakarta.persistence.*;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalTime;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Table(name = "parking_lot")
public class ParkingLot extends BaseDeleteEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "owner_id", nullable = false)
private User owner;
@Column(nullable = false, length = 100)
private String name;
@Column(nullable = false, length = 200)
private String address;
private Double latitude;
private Double longitude;
@Column(nullable = false)
private LocalTime openedAt;
@Column(nullable = false)
private LocalTime closedAt;
@Column(nullable = false)
private BigDecimal pricePerHour;
@Lob
@Column(nullable = false)
private String description;
@Column(nullable = false)
private Integer quantity;
@Enumerated(EnumType.STRING)
private ChargeType chargeType;
@Enumerated(EnumType.STRING)
private SourceType sourceType;
@Enumerated(EnumType.STRING)
private ParkingLotStatus status;
@Builder
private ParkingLot(User owner, String name, String address,
Double latitude, Double longitude,
LocalTime openedAt, LocalTime closedAt,
BigDecimal pricePerHour, String description,
Integer quantity, ChargeType chargeType,
SourceType sourceType) {
this.owner = owner;
this.name = name;
this.address = address;
this.latitude = latitude;
this.longitude = longitude;
this.openedAt = openedAt;
this.closedAt = closedAt;
this.pricePerHour = pricePerHour;
this.description = description;
this.quantity = quantity;
this.chargeType = chargeType;
this.sourceType = sourceType;
this.status = ParkingLotStatus.OPEN;
}
}
@Builder의 사용 이유
ParkingLot 엔티티에서 객체 생성이 복잡하진 않지만, 필드의 수가 많기 때문에
생성자에 모든 인자를 한번에 전달 할 경우 가독성이 떨어질 수 있다.
따라서 `@AllArgsConstructor` 보다 `@Builder`을 사용하여
엔티티를 생성할 때, 명시적으로 값을 설정할 수 있게 하였다.
이렇게 Builder 패턴을 사용하면, 코드를 더 읽기가 쉽고,
객체를 생성 할 때, 인자 순서에 의한 실수를 방지할 수 있다.
🤔 그렇다면 언제 @AllArgsConstructor를 사용할까?
클래스의 모든 필드를 한 번에 초기화 해야 하는 간단한 경우일 때,
@AllArgsConstructor 가 유용하다.
해당 어노테이션이 모든 필드를 매개변수로 받는 생성자를 자동으로 생성하여 객체를 빠르게 생성할 수 있다.
3. 객체 생성 제어 관점에서의 @Builder와 @AllArgsConstructor
`@Builder`는 생성자를 private이나 protected로 설정하여,
외부에서 직접 인스턴스를 생성하지 못하게하고,
반드시 Builder를 통해서만 객체를 생성할 수 있도록 강제할 수 있다.
이러한 방식을 통해 객체 생성 시 불필요한 변경이나, 잘못된 초기화를 방지하고,
객체 생성 로직을 한 곳에서 관리할 수 있다.
반대로 `@AllArgsConstructor`는 모든 필드를 인자로 받는 생성자를 public 접근 제어자를 적용하여 만든다.
따라서, 어디에서든 이 생성자를 호출하여 객체를 생성할 수 있게 되기에, 객체 생성의 제어가 어렵다.
하지만, DTO등 간단한 데이터 전달 목적의 인스턴스를 생성하는 경우라면 사용하여도 상관없다.