생성자 복잡성 관리: 너무 많은 인수는 언제 너무 많은가?

소프트웨어 개발, 특히 객체 지향 프로그래밍에서 생성자는 객체를 초기화하는 데 중추적인 역할을 합니다. 하지만 프로젝트가 발전하고 요구사항이 늘어남에 따라, 공통적인 문제가 발생합니다: 생성자 인수는 몇 개가 너무 많은가? 많은 생성자 매개변수를 요구하는 클래스는 빠르게 복잡해져 유지보수와 효율적인 사용이 어려워질 수 있습니다. 이 블로그 포스트에서는 이 문제를 논의하고 생성자 사용을 간소화하여 코드를 더 깔끔하고 관리하기 쉽게 만드는 두 가지 디자인 패턴을 살펴보겠습니다.

문제: 압도적인 생성자

Customer라는 이름의 클래스를 고려해봅시다. 이 클래스에는 비즈니스 논리에 부합하기 위해 필수적인 여러 필드가 포함되어 있습니다:

  • 사용자 이름
  • 이메일
  • 이름

이 네 개의 필드를 생성자에 부여하는 것은 간단해 보일 수 있지만, 필수 필드가 더 많아짐에 따라 상황은 번거로워질 수 있습니다. 클래스가 20개 이상의 매개변수를 축적하면 그 생성자를 사용하는 것은 개발자에게 큰 스트레스가 됩니다.

너무 많은 생성자 인수의 위험

  • 복잡성 증가: 매개변수가 너무 많은 생성자는 읽기에 어렵고 이해하기 힘들 수 있습니다.
  • 오류 발생 가능성: 매개변수의 순서를 잘못 입력하거나 필수 필드를 제공하는 것을 잊어버리면서 개발자 오류가 발생할 수 있습니다.
  • 유지보수 문제: 부풀려진 생성자를 가진 클래스를 수정하는 것은 코드베이스의 여러 장소에서 상당한 조정이 필요할 수 있습니다.

이 복잡성을 해결할 방법을 고민하면서, 생성자 인수를 제한하거나 사용을 간소화할 수 있는 대체 디자인 접근 방식이 있는지 궁금할 수 있습니다.

해결책: 빌더 및 유창한 인터페이스 패턴

  1. 빌더 패턴: 개별 필드를 사용하고 객체를 단계별로 구축함으로써 객체를 생성하는 보다 유연하고 읽기 쉬운 방법을 제공합니다.
  2. 유창한 인터페이스 패턴: 이 패턴은 자연어처럼 읽히는 코드를 장려하여 명확성을 향상시킵니다.

유창한 인터페이스 예제

아래의 예제에서는 Customer 객체를 생성하기 위한 CustomerBuilder를 구현하는 방법을 시연합니다:

public class CustomerBuilder {
    String surname;
    String firstName;
    String ssn;

    public static CustomerBuilder customer() {
        return new CustomerBuilder();
    }

    public CustomerBuilder withSurname(String surname) {
        this.surname = surname; 
        return this; 
    }

    public CustomerBuilder withFirstName(String firstName) {
        this.firstName = firstName;
        return this; 
    }

    public CustomerBuilder withSsn(String ssn) {
        this.ssn = ssn; 
        return this; 
    }

    // 클라이언트는 Customer를 직접 인스턴스화할 수 없습니다
    public Customer build() {
        return new Customer(this);            
    }
}

public class Customer {
    private final String firstName;
    private final String surname;
    private final String ssn;

    Customer(CustomerBuilder builder) {
        if (builder.firstName == null) throw new NullPointerException("firstName");
        if (builder.surname == null) throw new NullPointerException("surname");
        if (builder.ssn == null) throw new NullPointerException("ssn");
        this.firstName = builder.firstName;
        this.surname = builder.surname;
        this.ssn = builder.ssn;
    }

    public String getFirstName() { return firstName; }
    public String getSurname() { return surname; }
    public String getSsn() { return ssn; }    
}

Customer Builder 사용하기

CustomerBuilder를 사용한 실제 객체 생성은 다음과 같이 보입니다:

import static com.acme.CustomerBuilder.customer;

public class Client {
    public void doSomething() {
        Customer customer = customer()
            .withSurname("Smith")
            .withFirstName("Fred")
            .withSsn("123XS1")
            .build();
    }
}

결론: 균형 잡기

생성자 인수를 현명하게 관리하는 것은 유지보수 가능하고 읽기 쉬운 코드를 만드는 데 필수적입니다. 빌더유창한 인터페이스와 같은 패턴을 활용함으로써 개발자들은 생성자의 복잡성을 줄이고 지나치게 많은 매개변수의 함정을 피할 수 있습니다.

다음 번에 여러 개의 필수 필드에 직면했을 때 이 패턴 중 하나의 구현을 고려해보세요. 당신의 코드는 더 깔끔할 뿐만 아니라 작업하기 더 즐거운 환경이 될 것입니다.