효과적인 Java의 빌더 패턴


137

나는 최근 Joshua Bloch의 Effective Java를 읽기 시작했습니다. 빌더 패턴 [책의 항목 2]에 대한 아이디어가 정말 흥미로 웠습니다. 내 프로젝트에서 구현하려고 시도했지만 컴파일 오류가 발생했습니다. 본질적으로 내가하려고했던 것은 다음과 같습니다.

여러 속성이있는 클래스 및 해당 빌더 클래스 :

public class NutritionalFacts {
    private int sodium;
    private int fat;
    private int carbo;

    public class Builder {
        private int sodium;
        private int fat;
        private int carbo;

        public Builder(int s) {
            this.sodium = s;
        }

        public Builder fat(int f) {
            this.fat = f;
            return this;
        }

        public Builder carbo(int c) {
            this.carbo = c;
            return this;
        }

        public NutritionalFacts build() {
            return new NutritionalFacts(this);
        }
    }

    private NutritionalFacts(Builder b) {
        this.sodium = b.sodium;
        this.fat = b.fat;
        this.carbo = b.carbo;
    }
}

위의 클래스를 사용하려고하는 클래스 :

public class Main {
    public static void main(String args[]) {
        NutritionalFacts n = 
            new NutritionalFacts.Builder(10).carbo(23).fat(1).build();
    }
}

다음과 같은 컴파일러 오류가 발생합니다.

효과적인 java.BuilderPattern.NutritionalFacts.Builder를 포함하는 둘러싸는 인스턴스가 필요합니다 NutritionalFacts n = new NutritionalFacts.Builder (10) .carbo (23) .fat (1) .build ();

메시지의 의미를 이해하지 못합니다. 설명 해주십시오. 위 코드는 그의 책에서 Bloch가 제안한 예제와 유사합니다.


답변:


171

빌더를 static클래스로 만드십시오 . 그런 다음 작동합니다. 정적이 아닌 경우 소유 클래스의 인스턴스가 필요하며 인스턴스가 없어야하며 빌더없이 인스턴스를 작성하는 것도 금지되어야합니다.

public class NutritionFacts {
    public static class Builder {
    }
}

참조 : 중첩 클래스


34
그리고, 사실 Builder입니다 static책 (14 페이지의 제 2 판에서 라인 10)의 예에.
Powerlord

27

Builder 클래스를 정적으로 만들어야하고 필드를 최종으로 설정하고 해당 값을 가져 오기위한 getter가 있어야합니다. 해당 값에 세터를 제공하지 마십시오. 이런 식으로 수업은 불변이 될 것입니다.

public class NutritionalFacts {
    private final int sodium;
    private final int fat;
    private final int carbo;

    public int getSodium(){
        return sodium;
    }

    public int getFat(){
        return fat;
    }

    public int getCarbo(){
        return carbo;
    }

    public static class Builder {
        private int sodium;
        private int fat;
        private int carbo;

        public Builder sodium(int s) {
            this.sodium = s;
            return this;
        }

        public Builder fat(int f) {
            this.fat = f;
            return this;
        }

        public Builder carbo(int c) {
            this.carbo = c;
            return this;
        }

        public NutritionalFacts build() {
            return new NutritionalFacts(this);
        }
    }

    private NutritionalFacts(Builder b) {
        this.sodium = b.sodium;
        this.fat = b.fat;
        this.carbo = b.carbo;
    }
}

이제 다음과 같이 속성을 설정할 수 있습니다.

NutritionalFacts n = new NutritionalFacts.Builder().sodium(10).carbo(15).
fat(5).build();

NutritionalFacts 필드를 공개하지 않겠습니까? 그들은 이미 최종적이며 여전히 불변입니다.
skia.heliou

final필드는 초기화 중에 항상 필드가 필요한 경우에만 의미가 있습니다. 그렇지 않은 경우 필드는이 아니어야합니다 final.
Piotrek Hryciuk

12

정적이 아닌 클래스에 정적 방식으로 액세스하려고합니다. 로 변경 Builder하면 static class Builder작동합니다.

Builder존재하는 인스턴스가 없으므로 사용 예제가 실패합니다 . 모든 실용적인 목적을위한 정적 클래스는 항상 인스턴스화됩니다. 정적으로 만들지 않으면 다음과 같이 말해야합니다.

Widget = new Widget.Builder(10).setparm1(1).setparm2(3).build();

Builder매번 새로운 것을 만들어야하기 때문입니다.




5

일단 아이디어가 생기면 실제로는 롬복이 @Builder훨씬 더 편리하다는 것을 알게 될 것 입니다.

@Builder 다음과 같은 코드로 클래스를 인스턴스화하는 데 필요한 코드를 자동으로 생성 할 수 있습니다.

Person.builder()
  .name("Adam Savage")
  .city("San Francisco")
  .job("Mythbusters")
  .job("Unchained Reaction")
 .build(); 

공식 문서 : https://www.projectlombok.org/features/Builder


4

이것은 둘러싸 기 유형을 만들 수 없음을 의미합니다. 즉, 먼저 "부모"클래스의 인스턴스를 승인 한 다음이 인스턴스에서 중첩 된 클래스 인스턴스를 작성할 수 있습니다.

NutritionalFacts n = new NutritionalFacts()

Builder b = new n.Builder(10).carbo(23).fat(1).build();

중첩 클래스


3
그는 다른 방식이 아니라 "사실"을 구성하기 위해 빌더가 필요하기 때문에 그다지 의미가 없습니다.
Bozho

5
빌더 패턴에 중점을두면 "메시지의 의미를 이해하지 못합니다"에만 집중하고 두 가지 솔루션 중 하나를 제시했습니다.
Damian Leszczyński-Vash

3

Builder 클래스는 정적이어야합니다. 지금은 실제로 그 이상으로 코드를 테스트 할 시간이 없지만 작동하지 않으면 알려 주시면 다시 살펴 보겠습니다.


1

나는 개인적으로 다른 두 가지 수업이있을 때 다른 접근법을 사용하는 것을 선호합니다. 따라서 정적 클래스가 필요하지 않습니다. 이것은 기본적으로 Class.Builder새 인스턴스를 만들어야 할 때 쓰기를 피하기 위한 것입니다.

public class Person {
    private String attr1;
    private String attr2;
    private String attr3;

    // package access
    Person(PersonBuilder builder) {
        this.attr1 = builder.getAttr1();
        // ...
    }

    // ...
    // getters and setters 
}

public class PersonBuilder (
    private String attr1;
    private String attr2;
    private String attr3;

    // constructor with required attribute
    public PersonBuilder(String attr1) {
        this.attr1 = attr1;
    }

    public PersonBuilder setAttr2(String attr2) {
        this.attr2 = attr2;
        return this;
    }

    public PersonBuilder setAttr3(String attr3) {
        this.attr3 = attr3;
        return this;
    }

    public Person build() {
        return new Person(this);
    }
    // ....
}

따라서 다음과 같이 빌더를 사용할 수 있습니다.

Person person = new PersonBuilder("attr1")
                            .setAttr2("attr2")
                            .build();

0

많은 사람들이 이미 여기에서 언급했듯이 수업을해야합니다 static. 작은 추가-원한다면 정적 방법이없는 약간 다른 방법이 있습니다.

이걸 고려하세요. withProperty(value)클래스 내에서 유형 설정자 와 같은 것을 선언하여 빌더를 구현하고 자체에 대한 참조를 리턴하게합니다. 이 접근법에는 스레드 안전하고 간결한 단일하고 우아한 클래스가 있습니다.

이걸 고려하세요:

public class DataObject {

    private String first;
    private String second;
    private String third;

    public String getFirst(){
       return first; 
    }

    public void setFirst(String first){
       this.first = first; 
    }

    ... 

    public DataObject withFirst(String first){
       this.first = first;
       return this; 
    }

    public DataObject withSecond(String second){
       this.second = second;
       return this; 
    }

    public DataObject withThird(String third){
       this.third = third;
       return this; 
    }
}


DataObject dataObject = new DataObject()
     .withFirst("first data")
     .withSecond("second data")
     .withThird("third data");

더 많은 Java Builder 예제 를 확인하십시오 .


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