아직 디자인 패턴에 대해 감이 잡히지 않은 개발자들은 직관성을 이유로 빌더 패턴을 남용하려는 경향이 있다.
빌더 패턴의 핵심은 편의성이 아닌, 복잡한 객체를 단계적으로 의미를 드러내며 생성하는 것이다.

아래와 같은 사용은 빌더 패턴의 의도를 벗어난, 사실상 오버엔지니어링에 가깝다.

var cat = Cat.builder()
    .name("Navi")
    .age(3),
    .weight(8.5f)
    .build();

필드 수가 적고 의미가 명확하다면 생성자만으로도 충분하다.

var cat = new Cat("Navi", 3, 8.5f);

여기에 빌더를 사용해버리면 가독성을 높이기보단, 오히려 코드량과 추상화 비용만 증가한다.


빌더가 진짜 필요한 상황은 다음과 같다.
-> 필드 수가 많고 대부분이 선택적일 때
-> 생성 순서나 조합에 따른 제약이 있을 때
-> 가독성이 객체 의미 이해에 직결될 때
-> 불변 객체를 강제하고자 할 때

대표적인 예가 DTO다.

var request = HttpRequest.builder()
    .method(POST)
    .url("/api/v1/users")
    .header("Authorization", token)
    .header("Content-Type", "application/json")
    .timeout(3_000)
    .retry(2)
    .build();


이런 경우에 생성자를 사용하면 객체의 의미는 오히려 코드 뒤로 숨겨진다.

var request = new HttpRequest(POST, "/api/v1/users", token, "application/json", 3000, 2);

매개변수 순서를 외워야만 쓸 수 있는 코드고, 유지보수 단계에선 최악이다.


또한 엔티티에 빌더 패턴을 적용하는 건 대부분의 경우 잘못된 선택이다.

엔티티의 핵심은 생성 시점부터 항상 유효한 상태(invariant)를 유지해야 한다는 점이다.
즉, 엔티티는 처음부터 완성된 상태로 태어나야 하는 객체다.

빌더를 사용하면 build()가 호출되기 전까지 객체는 불완전한 상태로 존재하고, 이 과정에서 엔티티가 가져야 할 불변성은 깨진다.

엔티티에서는 생성자를 통해 필수 조건을 강제하거나, 팩토리 메서드를 통해 생성 경로를 명시하는 것도 좋은 방법이다.

var user = User.createWithEmail(email, password);
var user = User.createFromCognito(cognitoSub, email, realName);

참조
-> https://refactoring.guru/ko/design-patterns/builder
-> https://refactoring.guru/ko/design-patterns/factory-method