효과적인 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.