IT박스

효과적인 Java의 빌더 패턴

itboxs 2020. 6. 24. 07:49
반응형

효과적인 Java의 빌더 패턴


나는 최근 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가 제안한 예제와 유사합니다.


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

public class NutritionFacts {
    public static class Builder {
    }
}

참조 : 중첩 클래스


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();

Intellij IDEA에서 내부 빌더를 생성하려면 다음 플러그인을 확인하십시오. https://github.com/analytically/innerbuilder


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

The example usage you give fails because there is no instance of Builder present. A static class for all practical purposes is always instantiated. If you don't make it static, you'd need to say:

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

Because you would need to construct a new Builder every time.


You need to declare the Builder inner class as static.

Consult some documentation for both non-static inner classes and static inner classes.

Basically the non-static inner classes instances cannot exist without attached outer class instance.


Once you've got an idea, in practice, you may find lombok's @Builder much more convenient.

@Builder lets you automatically produce the code required to have your class be instantiable with code such as:

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

Official documentation: https://www.projectlombok.org/features/Builder


The Builder class should be static. I don't have time right now to actually test the code beyond that, but if it doesn't work let me know and I'll take another look.


This mean that you cant create enclose type. This mean that first you have to cerate a instance of "parent" class and then from this instance you can create nested class instances.

NutritionalFacts n = new NutritionalFacts()

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

Nested Classes


I personally prefer to use the other approach, when you have 2 different classes. So you don't need any static class. This is basically to avoid write Class.Builder when you has to create a new instance.

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);
    }
    // ....
}

So, you can use your builder like this:

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

As many already stated here you need to make the class static. Just small addition - if you want, there is a bit different way without static one.

Consider this. Implementing a builder by declaring something like withProperty(value) type setters inside the class and make them return a reference to itself. In this approach, you have a single and an elegant class which is a thread safe and concise.

Consider this:

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");

Check it out for more Java Builder examples.


You need to change Builder class to static class Builder. Then it will work fine.

참고URL : https://stackoverflow.com/questions/5007355/builder-pattern-in-effective-java

반응형