[hot deal] 새로운 객체 생성에 Builder 패턴을 사용하지 않은 이유

최근 프로젝트에서 객체 생성 시 Builder 패턴을 도입할지 고민했지만, 필수 필드 누락으로 인해 객체가 잘못 생성될 위험이 있어 Builder 패턴을 사용하지 않기로 결정했습니다. 이에 대해 간단히 기록해 보았습니다.

 

1. Builder 패턴의 장점

1.1. 가독성 향상

Builder 패턴은 복잡한 객체의 생성 과정을 가독성 높게 표현할 수 있습니다.

// 예시: Builder 패턴을 이용한 객체 생성
Member member = new Member.Builder()
                        .id(1L)
                        .name("Yeseul Hong")
                        .email("yeseul@example.com")
                        .build();

1.2. 유연성 제공

필드가 많은 객체를 생성할 때, 필요한 필드만 선택적으로 설정할 수 있어 유연성이 뛰어납니다. 필드 추가나 순서를 변경할 필요가 없는 경우에 유리하며, 불필요한 생성자 오버로딩을 줄일 수 있습니다.

 

2. 고려했던 Builder 패턴의 단점

2.2. 필수 필드 누락 가능성

Builder 패턴은 선택적인 필드를 설정하기에 유연하지만, 필수 필드까지도 선택적으로 설정할 수 있는 위험이 있습니다. 따라서 필수 필드가 누락된 상태에서도 객체가 생성될 가능성이 있어, Builder 패턴을 사용하지 않고 개발 해보자는 생각이 들었습니다.

// 필수 필드가 빠진 상태로 객체가 생성될 수 있음
Member member = new Member.Builder()
                        .email("john@example.com")
                        .build(); // name 필드 누락

 

Builder 패턴을 이용하면 필수 필드를 설정하지 않았을 때 컴파일 타임에 오류가 발생하지 않으며,

런타임에서만 오류가 발생할 수 있습니다.

 

3. Builder 패턴 대신 선택한 대안

이번 프로젝트에서는 생성자정적 팩토리 메서드를 사용해 필수 필드 설정을 강제했습니다. 객체 생성 시 필수 필드를 반드시 매개변수로 받아 객체 무결성을 보장하게 됩니다.

해당 프로젝트를 진행하면서 두 가지의 방법으로 객체를 생성하고자 했습니다.

 

3.1. 생성자를 통한 필수 필드 강제화

생성자에서 필수 필드를 매개변수로 받도록 설정하면, 객체 생성 시 모든 필수 필드를 명시해야 하므로, 필수 필드가 누락될 가능성을 원천적으로 차단할 수 있습니다.

public class Member {
    private Long id;
    private String name;
    private String email;

    public Member(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

3.2.정적 팩토리 메서드 사용

정적 팩토리 메서드를 사용하여 객체 생성을 캡슐화하고, 명시적으로 필수 필드를 전달받아 초기화할 수 있습니다. 정적 메서드 이름을 통해 객체 생성의 의도를 명확히 전달할 수도 있습니다.

public class Member {
    private Long id;
    private String name;
    private String email;

    private Member(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public static Member ofNameAndEmail(String name, String email) {
        return new Member(name, email);
    }
}

 

정적 팩토리 메서드 네이밍 규칙

정적 팩토리 메서드에는 다른 메서드와 구분하기 위한 네이밍 규칙이 있으며, 이 규칙은 거의 법칙처럼 사용됩니다.

따라서 각 네이밍의 역할을 이해하고 적절하게 활용하는 것이 중요합니다.

  • from: 하나의 매개변수를 전달 받아 객체 생성
  • of: 여러 개의 매개변수를 전달 받아 객체 생성
  • getInstance | instance: 인스턴스 생성 (이전에 반환했던 것과 같을 수 있음)
  • newInstance | create: 새로운 인스턴스 생성
  • get{생성할 객체의 타입}: 다른 타입의 인스턴스 생성 (이전에 반환했던 것과 같을 수 있음)
  • new{생성할 객체의 타입}: 다른 타입의 새로운 인스턴스 생성

정적 팩토리 메서드의 네이밍 규칙이 법칙처럼 자리 잡은 이유는,

메서드 이름만 보고도 그 역할을 쉽게 이해할 수 있어 코드와 API 문서의 가독성을 높이기 때문입니다.

 



일반 생성자 사용 시 불편한 점은 다음과 같습니다.

1. 메서드를 호출할 때 파라미터 할당 과정에서 매개변수의 순서를 잘못 입력하거나 누락할 가능성이 있어, 휴먼 에러가 발생할 수 있습니다.
2. 코드의 가독성이 떨어져 이해하기 어려울 수 있습니다.

이러한 불편함 때문에 빌더 패턴이 만들어졌습니다. 일반 생성자의 문제점을 먼저 경험해 보고, 이후 빌더 패턴으로 변경하여 각각의 장단점을 비교해보려 합니다.