생성자(Constructor), 정적 팩토리 메서드, 그리고 빌더 패턴
안녕하세요, 오늘은 Java에서 인스턴스를 생성하는 다양한 방법에 대해 알아보겠습니다. 주제는 생성자(Constructor), 정적 팩토리 메서드, 그리고 빌더(Builder) 패턴입니다. 각 방법의 장단점과 사용 시기를 설명하고, 실무에서의 적용 방안을 제시하겠습니다.
생성자(Constructor) vs 정적 팩토리 메서드
Java에서 인스턴스를 생성하는 가장 기본적인 방법은 생성자와 정적 팩토리 메서드입니다. 두 방식은 객체를 생성하는 데 있어 서로 다른 장단점을 가지고 있습니다.
정적 팩토리 메서드의 장점
- 이름(Naming)을 가질 수 있다: 생성자에 매개변수를 넘기기만 해서는 반환될 객체의 특성을 제대로 설명하지 못합니다. 반면, 정적 팩토리는 이름을 통해 반환될 객체의 특성을 명확히 설명할 수 있습니다.
- 호출될 때마다 새로운 인스턴스를 생성하지 않아도 된다: 불변 클래스는 인스턴스를 미리 만들어 놓거나 생성된 인스턴스를 캐싱하여 재활용할 수 있어 불필요한 객체 생성을 피할 수 있습니다.
- 반환 타입의 하위 타입 객체를 반환할 수 있다: 반환할 객체의 구체적인 타입을 숨길 수 있어 유연한 설계가 가능합니다.
- 입력 매개변수에 따라 매번 다른 클래스의 인스턴스를 반환할 수 있다: 매개변수 값에 따라 다른 타입의 객체를 반환할 수 있는 유연성을 제공합니다.
정적 팩토리 메서드의 단점
- 상속할 수 없다: 생성자의 접근 제한자가 private로 선언되기 때문에 하위 클래스에서 상속할 수 없습니다.
- 정적 팩토리 메서드를 찾기 어렵다: 생성자와 달리 메서드명은 자유롭게 정의되기 때문에, 메서드가 인스턴스를 생성하는 역할을 한다는 것을 파악하기 어려울 수 있습니다.
예제 코드: 정적 팩토리 메서드
@Getter
public class PaymentRequestDto {
private final String pgCorpName;
private PaymentRequestDto(String name) {
this.pgCorpName = PgCorp.valueOf(name.toUpperCase()).toString().toLowerCase();
}
public static PaymentRequestDto of(String pgCorpName) {
return new PaymentRequestDto(pgCorpName);
}
}
Builder 패턴
생성자와 정적 팩토리는 선택적 매개변수가 많아지면 한계가 있습니다. 이때 유용한 것이 바로 빌더 패턴입니다.
Builder 패턴의 장점
- 가독성과 안전성: 매개변수의 순서를 혼동할 가능성을 줄여줍니다. 각 필드를 메서드 체이닝으로 설정할 수 있어 코드의 가독성이 높아집니다.
- 불변성을 유지할 수 있다: setter를 사용하지 않고도 객체를 불변으로 유지할 수 있습니다. new를 사용하여 객체를 생성하기 때문에 성능 저하가 발생할 수 있지만, 객체의 안정성과 명확성을 보장할 수 있습니다.
Builder 패턴 구현 방법
- 클래스 내부에서 구현: 클래스 내부에 Builder 클래스를 작성하여 메서드 체이닝으로 객체를 생성합니다.
- Lombok 사용: Lombok의 @Builder 어노테이션을 사용하여 코드를 간소화할 수 있습니다.
예제 코드: Builder 패턴
public class ResponsePaymentApproveMessage {
private final String mId;
private final String currency;
private final String method;
private final String lastTransactionKey;
private final String paymentKey;
private final int totalAmount;
private final int balanceAmount;
private final int suppliedAmount;
private final int taxFreeAmount;
private final String requestedAt;
private final String approvedAt;
private ResponsePaymentApproveMessage(Builder builder) {
this.mId = builder.mId;
this.currency = builder.currency;
this.method = builder.method;
this.lastTransactionKey = builder.lastTransactionKey;
this.paymentKey = builder.paymentKey;
this.totalAmount = builder.totalAmount;
this.balanceAmount = builder.balanceAmount;
this.suppliedAmount = builder.suppliedAmount;
this.taxFreeAmount = builder.taxFreeAmount;
this.requestedAt = builder.requestedAt;
this.approvedAt = builder.approvedAt;
}
public static class Builder {
private String mId;
private String currency;
private String method;
private String lastTransactionKey;
private String paymentKey;
private int totalAmount;
private int balanceAmount;
private int suppliedAmount;
private int taxFreeAmount;
private String requestedAt;
private String approvedAt;
public Builder mId(String mId) { this.mId = mId; return this; }
public Builder currency(String currency) { this.currency = currency; return this; }
// ... 나머지 필드에 대한 메서드 체이닝
public ResponsePaymentApproveMessage build() {
return new ResponsePaymentApproveMessage(this);
}
}
}
Builder 패턴의 사용 시기
- 선택적 필드가 많아 가독성을 높이고자 할 때
- 코드의 불변성을 유지하면서 객체를 명확하게 생성할 때
결론
각 객체 생성 방법의 장단점을 이해하고, 상황에 맞게 생성자, 정적 팩토리 메서드, 또는 빌더 패턴을 사용하는 것은 코드의 가독성과 유지보수성에 큰 영향을 미침
'Spring' 카테고리의 다른 글
JPA에서 @Modifying 사용법과 동작 원리 (0) | 2024.12.03 |
---|---|
Spring에서 @RequiredArgsConstructor 사용법과 장점 (0) | 2024.12.03 |
spring boot 버전에 호환되는 의존성 버전 확인하는 법 (0) | 2024.11.29 |
람다와 스트림 (0) | 2024.11.08 |
동시성 문제 (4) | 2024.11.07 |