문제

동기 부여

최근에 생성자에 많은 매개변수를 전달하지 않고 복잡한 객체를 초기화하는 방법을 검색했습니다.빌더 패턴으로 시도했지만 실제로 필요한 모든 값을 설정했는지 컴파일 타임에 확인할 수 없다는 사실이 마음에 들지 않습니다.

전통적인 빌더 패턴

빌더 패턴을 사용하여 Complex 객체의 생성은 인수가 무엇에 사용되는지 확인하기가 더 쉽기 때문에 더 "형식 안전"합니다.

new ComplexBuilder()
        .setFirst( "first" )
        .setSecond( "second" )
        .setThird( "third" )
        ...
        .build();

하지만 이제는 중요한 매개변수를 쉽게 놓칠 수 있다는 문제가 있습니다.내부에서 확인해볼 수 있어요 build() 메소드이지만 런타임에만 가능합니다.컴파일 타임에는 뭔가를 놓친 경우 경고 메시지가 표시되지 않습니다.

향상된 빌더 패턴

이제 내 생각은 필요한 매개변수를 놓쳤을 때 "알려주는" 빌더를 만드는 것이었습니다.내 첫 번째 시도는 다음과 같습니다.

public class Complex {
    private String m_first;
    private String m_second;
    private String m_third;

    private Complex() {}

    public static class ComplexBuilder {
        private Complex m_complex;

        public ComplexBuilder() {
            m_complex = new Complex();
        }

        public Builder2 setFirst( String first ) {
            m_complex.m_first = first;
            return new Builder2();
        }

        public class Builder2 {
            private Builder2() {}
            Builder3 setSecond( String second ) {
                m_complex.m_second = second;
                return new Builder3();
            }
        }

        public class Builder3 {
            private Builder3() {}
            Builder4 setThird( String third ) {
                m_complex.m_third = third;
                return new Builder4();
            }
        }

        public class Builder4 {
            private Builder4() {}
            Complex build() {
                return m_complex;
            }
        }
    }
}

보시다시피 빌더 클래스의 각 설정자는 서로 다른 내부 빌더 클래스를 반환합니다.각 내부 빌더 클래스는 정확히 하나의 setter 메소드를 제공하고 마지막 클래스는 build() 메소드만 제공합니다.

이제 객체의 구성은 다시 다음과 같습니다:

new ComplexBuilder()
    .setFirst( "first" )
    .setSecond( "second" )
    .setThird( "third" )
    .build();

...하지만 필요한 매개변수를 잊어버릴 수 있는 방법은 없습니다.컴파일러는 그것을 받아들이지 않을 것입니다.

선택적 매개변수

선택적 매개변수가 있는 경우 마지막 내부 빌더 클래스를 사용합니다. Builder4 "전통적인" 빌더처럼 설정하여 자신을 반환합니다.

질문

  • 이게 잘 알려진 패턴인가요?특별한 이름이 있나요?
  • 함정이 보이나요?
  • 코드 줄을 줄여 구현을 개선할 수 있는 아이디어가 있습니까?
도움이 되었습니까?

해결책

아니요, 새로운 것이 아닙니다. 당신이 실제로하는 일은 일종의 DSL 표준 빌더 패턴을 확장하여 브랜치를 지원하여 빌더가 실제 객체에 대해 충돌하는 설정 세트를 생성하지 않도록하는 훌륭한 방법입니다.

개인적으로 나는 이것이 빌더 패턴에 대한 훌륭한 확장이라고 생각하며 모든 종류의 흥미로운 일을 할 수 있습니다. 예를 들어 직장에서 우리는 DSL Builder가 있습니다. assertMachine().usesElectricity().and().makesGrindingNoises().whenTurnedOn();. 좋아, 아마도 가장 좋은 예는 아니지만 요점을 얻는 것 같아요.

다른 팁

전통적인 빌더 패턴은 이미 이것을 처리합니다. 단순히 생성자에서 필수 매개 변수를 가져옵니다. 물론, 발신자가 NULL을 통과시키는 것을 방해하는 것은 없지만 방법도 마찬가지입니다.

내가 당신의 방법으로 볼 수있는 가장 큰 문제는 필수 매개 변수의 수를 가진 클래스의 조합 폭발이 있거나 사용자가 성가신 하나의 특정 sqeuence에서 매개 변수를 설정하도록 강요한다는 것입니다.

또한 많은 추가 작업입니다.

public class Complex {
    private final String first;
    private final String second;
    private final String third;

    public static class False {}
    public static class True {}

    public static class Builder<Has1,Has2,Has3> {
        private String first;
        private String second;
        private String third;

        private Builder() {}

        public static Builder<False,False,False> create() {
            return new Builder<>();
        }

        public Builder<True,Has2,Has3> setFirst(String first) {
            this.first = first;
            return (Builder<True,Has2,Has3>)this;
        }

        public Builder<Has1,True,Has3> setSecond(String second) {
            this.second = second;
            return (Builder<Has1,True,Has3>)this;
        }

        public Builder<Has1,Has2,True> setThird(String third) {
            this.third = third;
            return (Builder<Has1,Has2,True>)this;
        }
    }

    public Complex(Builder<True,True,True> builder) {
        first = builder.first;
        second = builder.second;
        third = builder.third;
    }

    public static void test() {
        // Compile Error!
        Complex c1 = new Complex(Complex.Builder.create().setFirst("1").setSecond("2"));

        // Compile Error!
        Complex c2 = new Complex(Complex.Builder.create().setFirst("1").setThird("3"));

        // Works!, all params supplied.
        Complex c3 = new Complex(Complex.Builder.create().setFirst("1").setSecond("2").setThird("3"));
    }
}

빌더 생성자에 "필요한"매개 변수를 넣지 않는 이유는 무엇입니까?

public class Complex
{
....
  public static class ComplexBuilder
  {
     // Required parameters
     private final int required;

     // Optional parameters
     private int optional = 0;

     public ComplexBuilder( int required )
     {
        this.required = required;
     } 

     public Builder setOptional(int optional)
     {
        this.optional = optional;
     }
  }
...
}

이 패턴은 요약되어 있습니다 효과적인 자바.

여러 클래스를 사용하는 대신 하나의 클래스와 여러 인터페이스를 사용합니다.많은 입력을 요구하지 않고도 구문을 적용합니다.또한 모든 관련 코드를 가까이서 볼 수 있으므로 더 큰 수준에서 코드에 무슨 일이 일어나고 있는지 더 쉽게 이해할 수 있습니다.

IMHO, 이것은 부풀어 오른 것 같습니다. 만약 너라면 가지다 모든 매개 변수를 사용하려면 생성자에 전달하십시오.

나는 이것을 보았다/사용했다 :

new ComplexBuilder(requiredvarA, requiedVarB).optional(foo).optional(bar).build();

그런 다음이를 요구하는 객체에 전달하십시오.

빌더 패턴은 일반적으로 선택적인 매개 변수가 많을 때 일반적으로 사용됩니다. 필요한 매개 변수가 많이 필요하다면 먼저이 옵션을 고려하십시오.

  • 당신의 수업은 너무 많은 일을하고있을 것입니다. 위반하지 않는지 다시 확인하십시오 단일 책임 원칙. 필요한 인스턴스 변수가 많은 클래스가 필요한 이유를 스스로에게 물어보십시오.
  • 당신은 생성자 일 수 있습니다 너무 많이하고 있습니다. 생성자의 작업은 구성됩니다. (그들은 이름을 지을 때 창의력을 발휘하지 못했습니다. d) 수업과 마찬가지로 방법은 단일 책임 원칙을 가지고 있습니다. 생성자가 단순한 필드 할당 이상의 작업을 수행하는 경우이를 정당화할만한 충분한 이유가 필요합니다. 당신은 당신이 필요하다는 것을 알 수 있습니다 공장 방법 건축업자보다는.
  • 당신의 매개 변수 일 수 있습니다 너무 적게하고 있습니다. 매개 변수를 작은 구조물로 그룹화 할 수 있는지 (또는 Java의 경우) 구조물과 같은 물체로 그룹화 할 수 있는지 스스로에게 물어보십시오. 작은 수업을 만드는 것을 두려워하지 마십시오. 구조물이나 작은 클래스를 만들어야한다면 잊지 마십시오 에게 리팩터 밖으로 기능 그것은 당신의 더 큰 클래스보다는 구조물에 속합니다.

자세한 내용은 빌더 패턴과 장점을 사용하는시기 다른 유사한 질문은 내 게시물을 확인해야합니다. 여기

질문 1 : 패턴의 이름과 관련하여 "Step Builder"라는 이름을 좋아합니다.

질문 2/3 : 함정과 권장 사항과 관련하여 이것은 대부분의 상황에서 복잡하다고 느낍니다.

  • 당신은 시행 중입니다 순서 내 경험에서 비정상적인 건축업자를 사용하는 방법. 어떤 경우에는 이것이 어떻게 중요한지 알 수 있었지만 결코 필요하지 않았습니다. 예를 들어, 나는 여기서 시퀀스를 강요 할 필요가 없다.

    Person.builder().firstName("John").lastName("Doe").build() Person.builder().lastName("Doe").firstName("John").build()

  • 그러나, 빌더는 가짜 물체가 건설되는 것을 방지하기 위해 일부 제약 조건을 시행해야했습니다. 어쩌면 필요한 모든 필드가 제공되거나 필드의 조합이 유효한지 확인하고 싶을 수도 있습니다. 나는 이것이 당신이 건물에 시퀀싱을 도입하고 싶은 진짜 이유라고 생각합니다.

    이 경우 Build () 메소드에서 검증을 수행하기 위해 Joshua Bloch의 권장 사항을 좋아합니다. 이 시점에서 모든 것이 사용할 수 있으므로 크로스 필드 검증에 도움이됩니다. 이 답변을 참조하십시오 : https://softwareengineering.stackexchange.com/a/241320

요약하면, 나는 당신이 빌더 방법에 대한 "누락"에 대해 걱정하고 있기 때문에 코드에 합병증을 추가하지 않을 것입니다. 실제로 이것은 테스트 사례로 쉽게 잡힐 수 있습니다. 바닐라 빌더로 시작한 다음 메소드 호출이 누락되어 계속 물린 경우이를 소개 할 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top