빌더 패턴은 많은 인수의 "문제"를 해결하지 않습니다. 그러나 왜 많은 논쟁이 문제가 되는가?
- 그들은 수업이 너무 많은 것을 나타냅니다 . 그러나 합법적으로 그룹화 할 수없는 많은 구성원을 포함하는 많은 유형이 있습니다.
- 많은 입력을 가진 함수를 테스트 하고 이해하는 것은 문자 그대로 기하 급수적으로 복잡해집니다!
- 언어가 명명 된 매개 변수를 제공하지 않으면 함수 호출은 자체 문서화 가 아닙니다 . 많은 인수를 가진 함수 호출을 읽는 것은 매우 어려운 일입니다. 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()
메서드 를 통해 추가 인수를 제공 해야합니다. 코딩 노력이 가장 적게 들지만 올바른 프로그램 을 작성하기가 더 어렵습니다 .
따라서“ 명명되지 않은 많은 매개 변수로 인해 코드를 유지하기가 어려워집니다 ”를 해결하는 방법이 많이 있지만 다른 문제는 남아 있습니다.
근본 문제에 접근
예를 들어, 테스트 가능성 문제 단위 테스트를 작성할 때 테스트 데이터를 주입하고 외부 부작용이있는 종속성 및 작업을 조롱하기 위해 테스트 구현을 제공 할 수 있어야합니다. 생성자 내에서 클래스를 인스턴스화 할 때 그렇게 할 수 없습니다. 클래스의 책임이 다른 객체를 만드는 것이 아니라면 사소한 클래스를 인스턴스화해서는 안됩니다. 이것은 단일 책임 문제와 함께 진행됩니다. 수업의 책임에 초점을 맞출수록 테스트하기가 더 쉬워지고 사용하기가 더 쉽습니다.
가장 쉽고 가장 좋은 방법은 생성자가 완전하게 구성된 종속성을 매개 변수 로 취하는 것입니다. 그러나이 방법은 호출자가 종속성을 관리해야 할 책임이 있습니다. 도메인 모델에서 종속성이 독립 엔터티가 아닌 한 이상적이지는 않습니다.
때로는 (추상적 인) 팩토리 또는 완전 의존성 주입 프레임 워크 가 대신 사용되지만, 대부분의 경우에 과도하게 사용될 수 있습니다. 특히, 이러한 인수 중 다수가 유사 전역 객체이거나 객체 인스턴스화간에 변경되지 않는 구성 값인 경우에는 인수 개수 만 줄입니다. 예 매개 변수 경우 a
와 d
글로벌 틱을했다, 우리가 얻을 것
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);
}
}
응용 프로그램에 따라, 이것은 의존성 관리자가 모든 것을 제공 할 수 있기 때문에 팩토리 메소드가 거의 인수를 갖지 않는 게임 체인저 일 수도 있고, 명백한 이점없이 인스턴스화를 복잡하게하는 대량의 코드 일 수도 있습니다. 이러한 팩토리는 매개 변수 관리보다 콘크리트 유형에 인터페이스를 맵핑하는 데 더 유용합니다. 그러나이 방법은 매우 유창한 인터페이스로 숨기지 않고 너무 많은 매개 변수의 근본 문제를 해결하려고합니다.