programing

Java Builder 클래스 하위 분류

firstcheck 2022. 8. 1. 21:21
반응형

Java Builder 클래스 하위 분류

이 Dobbs 박사의 기사, 특히 Builder Pattern에 대해 알려 주십시오. Builder를 하위 분류하는 경우 어떻게 대처해야 합니까?GMO 라벨링을 추가하기 위해 서브클래스로 분류하는 예제의 축소 버전을 사용하면 다음과 같은 간단한 구현이 가능합니다.

public class NutritionFacts {                                                                                                    

    private final int calories;                                                                                                  

    public static class Builder {                                                                                                
        private int calories = 0;                                                                                                

        public Builder() {}                                                                                                      

        public Builder calories(int val) { calories = val; return this; }                                                                                                                        

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

    protected NutritionFacts(Builder builder) {                                                                                  
        calories = builder.calories;                                                                                             
    }                                                                                                                            
}

서브클래스:

public class GMOFacts extends NutritionFacts {                                                                                   

    private final boolean hasGMO;                                                                                                

    public static class Builder extends NutritionFacts.Builder {                                                                 

        private boolean hasGMO = false;                                                                                          

        public Builder() {}                                                                                                      

        public Builder GMO(boolean val) { hasGMO = val; return this; }                                                           

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

    protected GMOFacts(Builder builder) {                                                                                        
        super(builder);                                                                                                          
        hasGMO = builder.hasGMO;                                                                                                 
    }                                                                                                                            
}

이제 코드를 다음과 같이 쓸 수 있습니다.

GMOFacts.Builder b = new GMOFacts.Builder();
b.GMO(true).calories(100);

그러나 순서가 틀리면 모든 것이 실패합니다.

GMOFacts.Builder b = new GMOFacts.Builder();
b.calories(100).GMO(true);

NutritionFacts.Builder를 반환하다NutritionFacts.Builder 이에요.GMOFacts.Builder이 문제를 해결하려면 어떻게 해야 할까요, 아니면 더 나은 패턴을 사용해야 할까요?

주의: 비슷한 질문에 대한답변은 위의 수업을 제공합니다.제 질문은 빌더의 호출 순서가 올바른지 확인하는 문제에 관한 것입니다.

제네릭으로 해결할 수 있어요.내 생각에 이건 '궁금하게 반복되는 일반적인 패턴'이라고 해

기본 클래스 작성기 메서드의 반환 형식을 일반 인수로 지정합니다.

public class NutritionFacts {

    private final int calories;

    public static class Builder<T extends Builder<T>> {

        private int calories = 0;

        public Builder() {}

        public T calories(int val) {
            calories = val;
            return (T) this;
        }

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

    protected NutritionFacts(Builder<?> builder) {
        calories = builder.calories;
    }
}

이제 파생된 클래스 빌더를 일반 인수로 사용하여 기본 빌더를 인스턴스화합니다.

public class GMOFacts extends NutritionFacts {

    private final boolean hasGMO;

    public static class Builder extends NutritionFacts.Builder<Builder> {

        private boolean hasGMO = false;

        public Builder() {}

        public Builder GMO(boolean val) {
            hasGMO = val;
            return this;
        }

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

    protected GMOFacts(Builder builder) {
        super(builder);
        hasGMO = builder.hasGMO;
    }
}

공식적으로 말하자면, 이 문제를 없애기 위해서

unchecked or unsafe operations 표시

★★★★★★★★★★★★★★★★의 경우return (T) this;@dimadima @ @Thomas N.에서 설명한 바와 같이 다음과 같은 솔루션이 특정 경우에 적용됩니다.

abstract 타입을 (일반 타입T extends Builder(이 를 선언합니다.protected abstract T getThis()다음 중 하나:

public abstract static class Builder<T extends Builder<T>> {

    private int calories = 0;

    public Builder() {}

    /** The solution for the unchecked cast warning. */
    public abstract T getThis();

    public T calories(int val) {
        calories = val;

        // no cast needed
        return getThis();
    }

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

상세한 것에 대하여는, http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205 를 참조해 주세요.

블로그 투고를 기반으로 하는 이 접근법에서는 모든 비리프 클래스는 추상적이고 모든 리프 클래스는 최종이어야 합니다.

public abstract class TopLevel {
    protected int foo;
    protected TopLevel() {
    }
    protected static abstract class Builder
        <T extends TopLevel, B extends Builder<T, B>> {
        protected T object;
        protected B thisObject;
        protected abstract T createObject();
        protected abstract B thisObject();
        public Builder() {
            object = createObject();
            thisObject = thisObject();
        }
        public B foo(int foo) {
            object.foo = foo;
            return thisObject;
        }
        public T build() {
            return object;
        }
    }
}

그런 다음 이 클래스와 해당 빌더를 확장하는 중간 클래스가 몇 개 더 있습니다.

public abstract class SecondLevel extends TopLevel {
    protected int bar;
    protected static abstract class Builder
        <T extends SecondLevel, B extends Builder<T, B>> extends TopLevel.Builder<T, B> {
        public B bar(int bar) {
            object.bar = bar;
            return thisObject;
        }
    }
}

마지막으로 부모에 대한 모든 구축 방법을 임의의 순서로 호출할 수 있는 구체적인 리프 클래스입니다.

public final class LeafClass extends SecondLevel {
    private int baz;
    public static final class Builder extends SecondLevel.Builder<LeafClass,Builder> {
        protected LeafClass createObject() {
            return new LeafClass();
        }
        protected Builder thisObject() {
            return this;
        }
        public Builder baz(int baz) {
            object.baz = baz;
            return thisObject;
        }
    }
}

다음으로 계층 내의 임의의 클래스에서 임의의 순서로 메서드를 호출할 수 있습니다.

public class Demo {
    LeafClass leaf = new LeafClass.Builder().baz(2).foo(1).bar(3).build();
}

때 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이calories()확장 빌더를 반환하도록 합니다.이는 Java가 공변 반환 유형을 지원하므로 컴파일됩니다.

public class GMOFacts extends NutritionFacts {
    private final boolean hasGMO;
    public static class Builder extends NutritionFacts.Builder {
        private boolean hasGMO = false;
        public Builder() {
        }
        public Builder GMO(boolean val)
        { hasGMO = val; return this; }
        public Builder calories(int val)
        { super.calories(val); return this; }
        public GMOFacts build() {
            return new GMOFacts(this);
        }
    }
    [...]
}

, , 하다, 하다, 하다, 하다, 하다, 하다, 하다, 하다, 라고 하는 식으로 수업을 만들 수 .Builder"상속보다 구성 선호"에 부합하는 패턴입니다.

클래스 의 인터페이스를 합니다.그 부모 클래스는Builder★★★★

public interface FactsBuilder<T> {

    public T calories(int val);
}

「 」의 NutritionFacts,Builder'Facts Builder'는 다음과 같습니다.

public class NutritionFacts {

    private final int calories;

    public static class Builder implements FactsBuilder<Builder> {
        private int calories = 0;

        public Builder() {
        }

        @Override
        public Builder calories(int val) {
            return this;
        }

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

    protected NutritionFacts(Builder builder) {
        calories = builder.calories;
    }
}

Builder같은 인터페이스를 확장해야 합니다(다양한 범용 실장 제외).

public static class Builder implements FactsBuilder<Builder> {
    NutritionFacts.Builder baseBuilder;

    private boolean hasGMO = false;

    public Builder() {
        baseBuilder = new NutritionFacts.Builder();
    }

    public Builder GMO(boolean val) {
        hasGMO = val;
        return this;
    }

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

    @Override
    public Builder calories(int val) {
        baseBuilder.calories(val);
        return this;
    }
}

해 주세요.NutritionFacts.Builder에 있는 입니다.GMOFacts.Builder (호출)baseBuilder)에서 FactsBuilder 콜 " " "baseBuilder같은 "Method" "Method" :

@Override
public Builder calories(int val) {
    baseBuilder.calories(val);
    return this;
}

에도 큰 .GMOFacts(Builder builder)에 대한 첫 번째 콜을 NutritionFacts.Builder:

protected GMOFacts(Builder builder) {
    super(builder.baseBuilder);
    hasGMO = builder.hasGMO;
}

「 」의 GMOFacts 링크:

public class GMOFacts extends NutritionFacts {

    private final boolean hasGMO;

    public static class Builder implements FactsBuilder<Builder> {
        NutritionFacts.Builder baseBuilder;

        private boolean hasGMO = false;

        public Builder() {
        }

        public Builder GMO(boolean val) {
            hasGMO = val;
            return this;
        }

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

        @Override
        public Builder calories(int val) {
            baseBuilder.calories(val);
            return this;
        }
    }

    protected GMOFacts(Builder builder) {
        super(builder.baseBuilder);
        hasGMO = builder.hasGMO;
    }
}

다중 빌더 상속의 전체 3단계 예는 다음과 같습니다.

(빌더의 카피 컨스트럭터가 있는 버전에 대해서는, 다음의 두 번째 예를 참조해 주세요).

첫 번째 레벨 - 부모(잠재적으로 추상적)

import lombok.ToString;

@ToString
@SuppressWarnings("unchecked")
public abstract class Class1 {
    protected int f1;

    public static class Builder<C extends Class1, B extends Builder<C, B>> {
        C obj;

        protected Builder(C constructedObj) {
            this.obj = constructedObj;
        }

        B f1(int f1) {
            obj.f1 = f1;
            return (B)this;
        }

        C build() {
            return obj;
        }
    }
}

제2레벨

import lombok.ToString;

@ToString(callSuper=true)
@SuppressWarnings("unchecked")
public class Class2 extends Class1 {
    protected int f2;

    public static class Builder<C extends Class2, B extends Builder<C, B>> extends Class1.Builder<C, B> {
        public Builder() {
            this((C) new Class2());
        }

        protected Builder(C obj) {
            super(obj);
        }

        B f2(int f2) {
            obj.f2 = f2;
            return (B)this;
        }
    }
}

제3레벨

import lombok.ToString;

@ToString(callSuper=true)
@SuppressWarnings("unchecked")
public class Class3 extends Class2 {
    protected int f3;

    public static class Builder<C extends Class3, B extends Builder<C, B>> extends Class2.Builder<C, B> {
        public Builder() {
            this((C) new Class3());
        }

        protected Builder(C obj) {
            super(obj);
        }

        B f3(int f3) {
            obj.f3 = f3;
            return (B)this;
        }
    }
}

그리고 사용 예시는

public class Test {
    public static void main(String[] args) {
        Class2 b1 = new Class2.Builder<>().f1(1).f2(2).build();
        System.out.println(b1);
        Class2 b2 = new Class2.Builder<>().f2(2).f1(1).build();
        System.out.println(b2);

        Class3 c1 = new Class3.Builder<>().f1(1).f2(2).f3(3).build();
        System.out.println(c1);
        Class3 c2 = new Class3.Builder<>().f3(3).f1(1).f2(2).build();
        System.out.println(c2);
        Class3 c3 = new Class3.Builder<>().f3(3).f2(2).f1(1).build();
        System.out.println(c3);
        Class3 c4 = new Class3.Builder<>().f2(2).f3(3).f1(1).build();
        System.out.println(c4);
    }
}


작성자를 위한 복사 생성자를 사용하는 조금 긴 버전:

첫 번째 레벨 - 부모(잠재적으로 추상적)

import lombok.ToString;

@ToString
@SuppressWarnings("unchecked")
public abstract class Class1 {
    protected int f1;

    public static class Builder<C extends Class1, B extends Builder<C, B>> {
        C obj;

        protected void setObj(C obj) {
            this.obj = obj;
        }

        protected void copy(C obj) {
            this.f1(obj.f1);
        }

        B f1(int f1) {
            obj.f1 = f1;
            return (B)this;
        }

        C build() {
            return obj;
        }
    }
}

제2레벨

import lombok.ToString;

@ToString(callSuper=true)
@SuppressWarnings("unchecked")
public class Class2 extends Class1 {
    protected int f2;

    public static class Builder<C extends Class2, B extends Builder<C, B>> extends Class1.Builder<C, B> {
        public Builder() {
            setObj((C) new Class2());
        }

        public Builder(C obj) {
            this();
            copy(obj);
        }

        @Override
        protected void copy(C obj) {
            super.copy(obj);
            this.f2(obj.f2);
        }

        B f2(int f2) {
            obj.f2 = f2;
            return (B)this;
        }
    }
}

제3레벨

import lombok.ToString;

@ToString(callSuper=true)
@SuppressWarnings("unchecked")
public class Class3 extends Class2 {
    protected int f3;

    public static class Builder<C extends Class3, B extends Builder<C, B>> extends Class2.Builder<C, B> {
        public Builder() {
            setObj((C) new Class3());
        }

        public Builder(C obj) {
            this();
            copy(obj);
        }

        @Override
        protected void copy(C obj) {
            super.copy(obj);
            this.f3(obj.f3);
        }

        B f3(int f3) {
            obj.f3 = f3;
            return (B)this;
        }
    }
}

그리고 사용 예시는

public class Test {
    public static void main(String[] args) {
        Class3 c4 = new Class3.Builder<>().f2(2).f3(3).f1(1).build();
        System.out.println(c4);

        // Class3 builder copy
        Class3 c42 = new Class3.Builder<>(c4).f2(12).build();
        System.out.println(c42);
        Class3 c43 = new Class3.Builder<>(c42).f2(22).f1(11).build();
        System.out.println(c43);
        Class3 c44 = new Class3.Builder<>(c43).f3(13).f1(21).build();
        System.out.println(c44);
    }
}

괄호나 세 개를 쳐다보고 싶지 않다면, 아니면 못 느끼겠다면...음... 내 말은...기침...나머지 팀원들은 기묘하게 반복되는 제네릭스의 패턴을 빠르게 이해할 수 있습니다.이렇게 하면 됩니다.

public class TestInheritanceBuilder {
  public static void main(String[] args) {
    SubType.Builder builder = new SubType.Builder();
    builder.withFoo("FOO").withBar("BAR").withBaz("BAZ");
    SubType st = builder.build();
    System.out.println(st.toString());
    builder.withFoo("BOOM!").withBar("not getting here").withBaz("or here");
  }
}

에 의해 지원되다

public class SubType extends ParentType {
  String baz;
  protected SubType() {}

  public static class Builder extends ParentType.Builder {
    private SubType object = new SubType();

    public Builder withBaz(String baz) {
      getObject().baz = baz;
      return this;
    }

    public Builder withBar(String bar) {
      super.withBar(bar);
      return this;
    }

    public Builder withFoo(String foo) {
      super.withFoo(foo);
      return this;
    }

    public SubType build() {
      // or clone or copy constructor if you want to stamp out multiple instances...
      SubType tmp = getObject();
      setObject(new SubType());
      return tmp;
    }

    protected SubType getObject() {
      return object;
    }

    private void setObject(SubType object) {
      this.object = object;
    }
  }

  public String toString() {
    return "SubType2{" +
        "baz='" + baz + '\'' +
        "} " + super.toString();
  }
}

및 부모 유형:

public class ParentType {
  String foo;
  String bar;

  protected ParentType() {}

  public static class Builder {
    private ParentType object = new ParentType();

    public ParentType object() {
      return getObject();
    }

    public Builder withFoo(String foo) {
      if (!"foo".equalsIgnoreCase(foo)) throw new IllegalArgumentException();
      getObject().foo = foo;
      return this;
    }

    public Builder withBar(String bar) {
      getObject().bar = bar;
      return this;
    }

    protected ParentType getObject() {
      return object;
    }

    private void setObject(ParentType object) {
      this.object = object;
    }

    public ParentType build() {
      // or clone or copy constructor if you want to stamp out multiple instances...
      ParentType tmp = getObject();
      setObject(new ParentType());
      return tmp;
    }
  }

  public String toString() {
    return "ParentType2{" +
        "foo='" + foo + '\'' +
        ", bar='" + bar + '\'' +
        '}';
  }
}

요점:

  • 상속을 통해 상위 유형으로 유지되는 개체의 필드를 설정할 수 없도록 개체를 빌더에 캡슐화합니다.
  • 슈퍼타입 빌더 메서드에 추가된 로직(있는 경우)이 서브타입에 유지되도록 하기 위한 콜.
  • 아래는 부모 클래스의 스플리어스 오브젝트 작성입니다...그러나 그것을 정리하는 방법은 아래를 참조해 주세요.
  • 위쪽은 한눈에 알 수 있고 자세한 생성자가 속성을 전송할 필요가 없습니다.
  • 빌더 개체에 액세스하는 여러 스레드가 있는 경우...내가 네가 아니라서 다행인 것 같아:)

편집:

나는 가짜 물체 생성에 대한 방법을 찾았다.먼저 이것을 각 빌더에 추가합니다.

private Class whoAmI() {
  return new Object(){}.getClass().getEnclosingMethod().getDeclaringClass();
}

그런 다음 각 빌더의 컨스트럭터에서 다음을 수행합니다.

  if (whoAmI() == this.getClass()) {
    this.obj = new ObjectToBuild();
  }

은 이용 file file file file file file file for 의 추가 클래스 입니다.new Object(){} 내부

각 클래스에 스태틱팩토리 메서드를 작성하는 방법이 있습니다.

NutritionFacts.newBuilder()
GMOFacts.newBuilder()

이 정적 공장 방식에서는 적절한 빌더가 반환됩니다. 하면 됩니다.GMOFacts.Builder확장NutritionFacts.Builder, 그것은 문제가 되지 않습니다.여기서의 문제는 가시성을 다루는 것입니다...

두 가지 형식 매개 변수를 사용할 수 있는 상위 추상 일반 빌더 클래스를 만들었습니다.첫 번째는 build()에 의해 반환되는 객체의 유형입니다.두 번째는 각 옵션 파라미터 세터에 의해 반환되는 유형입니다.다음은 설명을 위한 부모 클래스와 자녀 클래스입니다.

// **Parent**
public abstract static class Builder<T, U extends Builder<T, U>> {
    // Required parameters
    private final String name;

    // Optional parameters
    private List<String> outputFields = null;


    public Builder(String pName) {
        name = pName;
    }

    public U outputFields(List<String> pOutFlds) {
        outputFields = new ArrayList<>(pOutFlds);
        return getThis();
    }


    /**
     * This helps avoid "unchecked warning", which would forces to cast to "T" in each of the optional
     * parameter setters..
     * @return
     */
    abstract U getThis();

    public abstract T build();



    /*
     * Getters
     */
    public String getName() {
        return name;
    }
}

 // **Child**
 public static class Builder extends AbstractRule.Builder<ContextAugmentingRule, ContextAugmentingRule.Builder> {
    // Required parameters
    private final Map<String, Object> nameValuePairsToAdd;

    // Optional parameters
    private String fooBar;


    Builder(String pName, Map<String, String> pNameValPairs) {
        super(pName);
        /**
         * Must do this, in case client code (I.e. JavaScript) is re-using
         * the passed in for multiple purposes. Doing {@link Collections#unmodifiableMap(Map)}
         * won't caught it, because the backing Map passed by client prior to wrapping in
         * unmodifiable Map can still be modified.
         */
        nameValuePairsToAdd = new HashMap<>(pNameValPairs);
    }

    public Builder fooBar(String pStr) {
        fooBar = pStr;
        return this;
    }


    @Override
    public ContextAugmentingRule build() {
        try {
            Rule r = new ContextAugmentingRule(this);
            storeInRuleByNameCache(r);
            return (ContextAugmentingRule) r;
        } catch (RuleException e) {
            throw new IllegalArgumentException(e);
        }
    }

    @Override
    Builder getThis() {
        return this;
    }
}

이것은 나의 요구를 만족스럽게 충족시켰다.

Java의 IEEE 기고문헌 Fluent Builder는 이 문제에 대한 포괄적인 해결책을 제공합니다.

원래 질문을 상속 부족과 준불변성의 두 가지 하위 문제로 분석하고 Java에서 코드 재사용을 통해 이 두 가지 하위 문제에 대한 솔루션이 상속 지원을 위해 어떻게 열리는지 보여줍니다.

언급URL : https://stackoverflow.com/questions/17164375/subclassing-a-java-builder-class

반응형