수많은 매개 변수와 빌더 패턴의 생성자


21

클래스에 많은 매개 변수가있는 생성자 (예 : 4 개 이상)가 있으면 코드 냄새 일 가능성이 높습니다 . 클래스가 SRP를 충족하는 경우 다시 고려해야 합니다.

그러나 10 개 이상의 매개 변수에 의존하고 결국 빌더 패턴을 통해 모든 매개 변수를 설정하는 것으로 끝나는 경우 어떻게해야합니까? Person개인 정보, 작업 정보, 친구 정보, 관심사 정보, 교육 정보 등 을 사용하여 유형 객체를 작성 한다고 상상해보십시오 . 이것은 이미 훌륭하지만 어떻게 든 4 개 이상의 매개 변수를 설정했습니다. 이 두 경우가 같은 것으로 간주되지 않는 이유는 무엇입니까?

답변:


25

빌더 패턴은 많은 인수의 "문제"를 해결하지 않습니다. 그러나 왜 많은 논쟁이 문제가 되는가?

  • 그들은 수업이 너무 많은 것을 나타냅니다 . 그러나 합법적으로 그룹화 할 수없는 많은 구성원을 포함하는 많은 유형이 있습니다.
  • 많은 입력을 가진 함수를 테스트 하고 이해하는 것은 문자 그대로 기하 급수적으로 복잡해집니다!
  • 언어가 명명 된 매개 변수를 제공하지 않으면 함수 호출은 자체 문서화아닙니다 . 많은 인수를 가진 함수 호출을 읽는 것은 매우 어려운 일입니다. 7 번째 매개 변수가 무엇을해야할지 모릅니다. 5 번째와 6 번째 인수가 실수로 바뀌 었는지, 특히 동적으로 유형이 지정된 언어이거나 모든 것이 문자열이거나 마지막 매개 변수가 true어떤 이유로 든 경우에는 알 수 없습니다.

가짜 명명 된 매개 변수

빌더 패턴 주소 이러한 문제 많은 인수 함수 호출 즉 유지 보수 문제의 한 * . 그래서 같은 함수 호출

MyClass o = new MyClass(a, b, c, d, e, f, g);

될 수 있습니다

MyClass o = MyClass.builder()
  .a(a).b(b).c(c).d(d).e(e).f(f).g(g)
  .build();

* 빌더 패턴은 원래 복합 객체를 조립하기위한 표현에 무관 한 접근 방식으로 의도되었는데, 이는 매개 변수에 대한 명명 된 인수보다 훨씬 큰 열망입니다. 특히 빌더 패턴에는 유연한 인터페이스가 필요하지 않습니다.

존재하지 않는 빌더 메소드를 호출하면 폭발 할 것이기 때문에 약간의 추가 안전을 제공하지만 그렇지 않으면 생성자 호출의 주석에없는 것이 없습니다. 또한 빌더를 수동으로 작성하려면 코드가 필요하며 더 많은 코드에는 항상 더 많은 버그가 포함될 수 있습니다.

새로운 값 유형을 쉽게 정의 할 수있는 언어에서는 마이크로 타이핑 / 작은 유형 을 사용하여 명명 된 인수를 시뮬레이션 하는 것이 더 낫다는 것을 알았습니다 . 유형이 실제로 작기 때문에 이름이 지정되지만 더 많이 입력하면 ;-)

MyClass o = new MyClass(
  new MyClass.A(a), new MyClass.B(b), new MyClass.C(c),
  new MyClass.D(d), new MyClass.E(e), new MyClass.F(f),
  new MyClass.G(g));

물론, 유형 이름 A, B, C당신이 매개 변수를 줘야 할 것 같은 ...하여 매개 변수, 종종 동일한 이름의 의미를 설명 자기 문서화 이름이어야합니다. 명명 된 인수 관용구와 비교할 때 필요한 구현이 훨씬 단순하므로 버그가 적을 가능성이 적습니다. 예를 들어 (Java-ish 구문 사용) :

class MyClass {
  ...
  public static class A {
    public final int value;
    public A(int a) { value = a; }
  }
  ...
}

컴파일러는 모든 인수가 제공되었음을 보장합니다. 빌더를 사용하면 누락 된 인수를 수동으로 확인하거나 상태 시스템을 호스트 언어 유형 시스템으로 인코딩해야합니다. 둘 다 버그를 포함 할 수 있습니다.

명명 된 인수를 시뮬레이션하는 또 다른 일반적인 방법이 있습니다. 인라인 클래스 구문을 사용하여 모든 필드를 초기화 하는 단일 추상 매개 변수 객체 입니다. 자바에서 :

MyClass o = new MyClass(new MyClass.Arguments(){{ argA = a; argB = b; argC = c; ... }});

class MyClass {
  ...
  public static abstract class Arguments {
    public int argA;
    public String ArgB;
    ...
  }
}

그러나 필드를 잊어 버릴 수 있으며 이것은 언어별로 솔루션입니다 (JavaScript, C # 및 C에서 사용되는 것을 보았습니다).

다행히도 생성자는 여전히 모든 인수의 유효성을 검사 할 수 있습니다. 개체가 부분적으로 구성된 상태로 생성 된 경우에는 그렇지 않으며 사용자가 setter 또는 init()메서드 를 통해 추가 인수를 제공 해야합니다. 코딩 노력이 가장 적게 들지만 올바른 프로그램 을 작성하기가 더 어렵습니다 .

따라서“ 명명되지 않은 많은 매개 변수로 인해 코드를 유지하기가 어려워집니다 ”를 해결하는 방법이 많이 있지만 다른 문제는 남아 있습니다.

근본 문제에 접근

예를 들어, 테스트 가능성 문제 단위 테스트를 작성할 때 테스트 데이터를 주입하고 외부 부작용이있는 종속성 및 작업을 조롱하기 위해 테스트 구현을 제공 할 수 있어야합니다. 생성자 내에서 클래스를 인스턴스화 할 때 그렇게 할 수 없습니다. 클래스의 책임이 다른 객체를 만드는 것이 아니라면 사소한 클래스를 인스턴스화해서는 안됩니다. 이것은 단일 책임 문제와 함께 진행됩니다. 수업의 책임에 초점을 맞출수록 테스트하기가 더 쉬워지고 사용하기가 더 쉽습니다.

가장 쉽고 가장 좋은 방법은 생성자가 완전하게 구성된 종속성을 매개 변수취하는 것입니다. 그러나이 방법은 호출자가 종속성을 관리해야 할 책임이 있습니다. 도메인 모델에서 종속성이 독립 엔터티가 아닌 한 이상적이지는 않습니다.

때로는 (추상적 인) 팩토리 또는 완전 의존성 주입 프레임 워크 가 대신 사용되지만, 대부분의 경우에 과도하게 사용될 수 있습니다. 특히, 이러한 인수 중 다수가 유사 전역 객체이거나 객체 인스턴스화간에 변경되지 않는 구성 값인 경우에는 인수 개수 만 줄입니다. 예 매개 변수 경우 ad글로벌 틱을했다, 우리가 얻을 것

Dependencies deps = new Dependencies(a, d);
...
MyClass o = deps.newMyClass(b, c, e, f, g);

class MyClass {
  MyClass(Dependencies deps, B b, C c, E e, F f, G g) {
    this.depA = deps.newDepA(b, c);
    this.depB = deps.newDepB(e, f);
    this.g = g;
  }
  ...
}

class Dependencies {
  private A a;
  private D d;
  public Dependencies(A a, D d) { this.a = a; this.d = d; }
  public DepA newDepA(B b, C c) { return new DepA(a, b, c); }
  public DepB newDepB(E e, F f) { return new DepB(d, e, f); }
  public MyClass newMyClass(B b, C c, E e, F f, G g) {
    return new MyClass(deps, b, c, e, f, g);
  }
}

응용 프로그램에 따라, 이것은 의존성 관리자가 모든 것을 제공 할 수 있기 때문에 팩토리 메소드가 거의 인수를 갖지 않는 게임 체인저 일 수도 있고, 명백한 이점없이 인스턴스화를 복잡하게하는 대량의 코드 일 수도 있습니다. 이러한 팩토리는 매개 변수 관리보다 콘크리트 유형에 인터페이스를 맵핑하는 데 더 유용합니다. 그러나이 방법은 매우 유창한 인터페이스로 숨기지 않고 너무 많은 매개 변수의 근본 문제를 해결하려고합니다.


나는 자기 문서화 부분에 대해 정말로 논쟁 할 것이다. 괜찮은 IDE + 괜찮은 주석이 있다면, 대부분의 경우 생성자와 매개 변수 정의는 한 번의 키 입력입니다.
JavierIEH 2016 년

Android Studio 3.0에서 생성자의 매개 변수 이름은 생성자 호출에서 전달되는 값 옆에 표시됩니다. 예를 들면 : 새로운 A (오퍼랜드 1:34, 피연산자 2:56); operand1 및 operand2는 생성자의 매개 변수 이름입니다. IDE에서 코드를 더 읽기 쉽게 보여줍니다. 따라서 매개 변수가 무엇인지 찾기 위해 정의로 이동할 필요가 없습니다.
가닛

9

빌더 패턴은 사용자를 위해 아무것도 해결하지 않으며 설계 실패를 수정하지 않습니다.

10 개의 매개 변수를 구성해야하는 클래스가있는 경우 빌더를 구성하여 작성하면 디자인이 갑자기 향상되지는 않습니다. 문제의 클래스를 리팩토링하도록 선택해야합니다.

반면에 클래스의 특정 속성이 선택적인 클래스, 아마도 간단한 DTO가있는 경우 빌더 패턴은 해당 오브젝트의 구성을 용이하게 할 수 있습니다.


1

개인 정보, 작업 정보 (각 고용 "명성"), 각 친구 등의 각 부분은 고유 한 개체로 변환되어야합니다.

업데이트 기능을 구현하는 방법을 고려하십시오. 사용자는 새로운 작업 기록 을 추가 하려고합니다 . 사용자는 정보의 다른 부분이나 이전 작업 기록을 변경하고 싶지 않습니다. 그대로 유지하십시오.

많은 정보가있을 때 한 번에 한 조각 씩 정보를 작성하는 것은 불가피합니다. 인간의 직관 (프로그래머가 사용하기 쉽도록) 또는 사용 패턴 (일반적으로 동시에 업데이트되는 정보)에 따라 논리적으로 그룹화 할 수 있습니다.

빌더 패턴은 유형이 지정된 인수 목록 (순서화 또는 비 순서화)을 캡처하는 방법 일 뿐이며 실제 생성자에 전달되거나 생성자가 빌더의 캡처 된 인수를 읽을 수 있습니다.

4의 지침은 단지 규칙입니다. 4 개 이상의 인수가 필요한 상황이 있으며이를 그룹화 할 수있는 건전하거나 논리적 인 방법이 없습니다. 이러한 경우 struct직접 또는 속성 설정기로 채울 수있는 파일 을 만드는 것이 좋습니다. 주목하십시오struct 이 경우 빌더는 매우 유사한 목적을 제공합니다.

그러나 귀하의 예에서는 그룹화하는 논리적 방법이 무엇인지 이미 설명했습니다. 이것이 사실이 아닌 상황에 처한 경우 다른 예를 들어 설명 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.