2017-12-04 18 views
0

최근 빌더 패턴이 매우 강한 상황이되었지만 서브 클래스 할 필요가있었습니다. 일부 솔루션을 찾아 보았고 몇 가지 제네릭을 제안했지만 다른 것은 일반적인 서브 클래 싱을 제안했습니다. 그러나 내가 본 예제 중 아무 것도 필드는 객체 작성을 시작하기 위해 필드가 필요했습니다. 내가 갇혀있는 곳을 설명하기 위해 작은 예제를 썼다. 매 회마다 잘못된 클래스를 반환하고 정적 메서드를 재정의 할 수 없으며 super()가 잘못된 데이터 형식을 반환하는 등 문제가있는 벽으로 계속 실행했습니다. 과도한 사용 이외의 방법은 없습니다. 제네릭 중.하위 분류 및 필수 매개 변수가있는 빌더 디자인 패턴?

이 상황에서 올바른 방법은 무엇입니까?

테스터

import person.Person; 
import person.Student; 

public class Tester 
{ 
    public static void main(String[] args) 
    { 
     Person p = Person.builder("Jake", 18).interest("Soccer").build(); 
     // Student s = Student.builder(name, age) <-- It's weird that we still have access to pointless static method 
     // Student s = Student.builder("Johnny", 24, "Harvard", 3).address("199 Harvard Lane") <-- returns Person builder, not student 
     Student s = ((Student.Builder)Student.builder("Jack", 19, "NYU", 1).address("Dormitory")).build(); // really bad 
    } 
} 

사람 클래스

package person; 

import java.util.ArrayList; 
import java.util.List; 

public class Person 
{ 
    // Required 
    protected String name; 
    protected int age; 

    // Optional 
    protected List<String> interests = new ArrayList<>(); 
    protected String address = ""; 

    protected Person(String name, int age) 
    { 
     this.name = name; 
     this.age = age; 
    } 

    public String getName() { return name; } 
    public int getAge() { return age; } 
    public List<String> getInterests() { return interests; } 
    public String getAddress() { return address; } 

    // person.person does not allow builder construction 
    // unless all required fields are provided 

    /* Problem: I have to repeat the constructor fields here, very annoying */ 
    public static Builder builder(String name, int age) 
    { 
     Person p = new Person(name, age); 
     return new Builder(p); 
    } 

    public static class Builder 
    { 
     Person reference; 

     protected Builder(Person reference) 
     { 
      this.reference = reference; 
     } 

     public Builder address(String address) 
     { 
      reference.address = address; 
      return this; 
     } 

     public Builder interest(String interest) 
     { 
      reference.interests.add(interest); 
      return this; 
     } 

     public Person build() 
     { 
      return reference; 
     } 
    } 
} 

학생 클래스

package person; 

import java.util.ArrayList; 
import java.util.List; 

public class Student extends Person 
{ 
    // Required 
    protected String school; 
    protected int year; 

    // Optional 
    protected List<String> subjects = new ArrayList<>(); 

    // This looks good 
    public Student(final String name, final int age, final String school, final int year) 
    { 
     super(name, age); 
     this.school = school; 
     this.year = year; 
    } 

    public String getSchool() { return school; } 
    public int getYear() { return year; } 
    public List<String> getSubjects() { return subjects; } 

    /* Here's where my issues are: 

     * Override doesn't compile on static methods but how else can I describe that I want to 
     * override this functionality from the Person class? 
     * 
     * Extending 'Person' does not enforce that I need to provide 'name', 'age', etc like it would 
     * if this was a normal design pattern using the 'new' keyword. I have to manually drag fields 
     * from 'person' and place them here. This would get VERY messy with an additional class 
     * 
     * User can STILL call the Person builder on a Student object, which makes no sense. */ 
    /*@Override*/ public static Builder builder(String name, int age, String school, int year) 
    { 
     Student s = new Student(name, age, school, year); 
     return new Builder(s); 
    } 

    public static class Builder extends Person.Builder 
    { 
     // Student reference; <--- this should not be needed since we already 
     //      have a variable for this purpose from 'Person.Builder' 

     public Builder(final Student reference) 
     { 
      super(reference); 
     } 

     /* Things begins to get very messy here */ 
     public Builder subject(String subject) 
     { 
      ((Student)reference).subjects.add(subject); 
      // I guess I could replace the reference with a student one, but 
      // I feel like that infringes on calling super() builder since we do the work twice. 
      return this; 
     } 

     @Override public Student build() 
     { 
      // I can either cast here or 
      // rewrite 'return reference' every time. 
      // Seems to infringe a bit on subclassing. 
      return (Student)super.build(); 
     } 
    } 
} 
+0

는 [빌더가 중첩 될 필요는 없다] 일부 코드 (https://stackoverflow.com/questions/19130876/builder-pattern-inside-vs- 외부 클래스), 반환 할 구현을 표시하는 컨텍스트가 필요합니다. 기본 클래스의 생성자는 유지하지만'setSchool()','setYear()'또는'addSubject()'가 호출되면 Person 대신 Student를 반환합니다. –

+0

빌더 패턴의 값 중 하나는 많은 생성자 매개 변수로 끝나지 않는다는 것입니다. – DwB

답변

1

여기에 쓴 내용 :

Student s = ((Student.Builder)Student.builder("Jack", 19, "NYU", 1).address("Dormitory")).build(); // really bad 

은 실제로 자연스럽지 않으므로 전송할 필요가 없습니다.

/* Things begins to get very messy here */ 
    public Builder subject(String subject) { 
     ((Student)reference).subjects.add(subject);   
     return this; 
    } 

    @Override public Student build() {   
     return (Student)super.build(); 
    } 

귀하의 주요 문제 사이의 결합입니다 : 모든 당신이 Student.Builder의 구현에 한 캐스트 외에도

Student s = Student.builder("Jack", 19, "NYU", 1).address("Dormitory")).build(); 

또한 런타임에 실패 할 수 있습니다 소음과 문은 다음과 같습니다
우리는 같은 아니라 뭔가 기대 Builder 클래스 및 건물 방법.
컴파일 타임에 컴파일러가 선택한 메서드 바인딩이 호출 대상의 선언 된 형식과 인수의 선언 된 형식에 따라 수행된다는 점을 고려해야합니다.
인스턴스화 된 유형은 동적 바인딩이 적용될 때 런타임에만 고려됩니다. 즉 런타임에 런타임 객체에서 컴파일 할 때 메서드를 호출합니다.


그래서 Student.Builder에 정의 된이 최우선 충분하지 않습니다 :

컴파일시에
Student.builder("Jack", 19, "NYU", 1).address("Dormitory").build(); 

address("Dormitory")Person.Builder로 입력 변수를 반환하는 방법에 정의 된대로 : 당신이 호출로

@Override public Student build() { 
    return (Student)super.build(); 
} 

Person.Builder :

public Builder address(String address){ 
    reference.address = address; 
    return this; 
} 

이며 Student.Builder에 덮어 쓰지 않습니다.런타임 물론

public Person build(){ 
    return reference; 
} 

, 반환 된 객체가 될 것입니다 : Person.Builder로 선언 된 변수에 build()를 호출
그리고 컴파일시는 방법으로 Person.Builder에 선언으로 선언을 입력 Person로 가진 개체를 반환 Student

같은

Student.builder("Jack", 19, "NYU", 1) 후드 Student 아닌 Person 하에서 생성한다.

public static class Builder { 

    Person.Builder personBuilder; 
    private Student reference; 

    public Builder(final Student reference) { 
    this.reference = reference; 
    personBuilder = new Person.Builder(reference); 
    } 

    public Builder subject(String subject) { 
    reference.subjects.add(subject); 
    return this; 
    } 

    // delegation to Person.Builder but return Student.Builder 
    public Builder interest(String interest) { 
    personBuilder.interest(interest); 
    return this; 
    } 

    // delegation to Person.Builder but return Student.Builder 
    public Builder address(String address) { 
    personBuilder.address(address); 
    return this; 
    } 

    public Student build() { 
    return (Student) personBuilder.build(); 
    } 

} 

는 이제 쓸 수 있습니다 : 심지어

Student s = Student.builder("Jack", 19, "NYU", 1) 
        .address("Dormitory") 
        .build(); 

또는 :


은 모두 구현과 클라이언트 측에서 Student.builder에 번지는 현상을 방지 할 inheritancy를 통해 구성을 선호하는

Student s2 = Student.builder("Jack", 19, "NYU", 1) 
        .interest("Dance") 
        .address("Dormitory") 
        .build(); 

컴포지션 소개 generall 상속으로 더 많은 코드가 있지만 코드는 으로 더욱 견고하고 적응력이 뛰어납니다.

부수적으로 실제 문제는 1 개월 전에 답변 한 다른 질문에 가까울 정도입니다.
The question and its answers 관심을 가질 수 있습니다.

0

배경

  1. 정적 방법으로 몇 가지 생각은 단위 테스트가 더 어려워 , 정말 대단하지 않다.
  2. 빌더를 정적, 중첩 된 클래스로 두는 것이 좋지만 빌더를 사용하여 클래스를 구성하는 경우에는 생성자를 public으로 설정해야합니다.
  3. 빌더를 동일한 패키지의 별도 클래스로 만들고 빌더가 작성한 클래스의 생성자 패키지 액세스를 선호합니다.
  4. 빌더 생성자 매개 변수를 제한하십시오.
  5. 저는 빌더에 클래스 계층 구조를 사용하는 팬이 아닙니다.
  6. Person 클래스와 Student 클래스에는 각각 빌더가 있습니다.

public class PersonBuilder 
{ 
    private String address; 
    private int age; 
    private final List<String> interestList; 
    private String name; 

    public PersonBuilder() 
    { 
     interestList = new LinkedList<>(); 
    } 

    public void addInterest(
     final String newValue) 
    { 
     // StringUtils is an apache utility. 
     if (StringUtils.isNotBlank(newValue)) 
     { 
      interestList.add(newValue); 
     } 

     return this; 
    } 

    public Person build() 
    { 
     // perform validation here. 
     // check for required values: age and name. 

     // send all parameters in the constructor. it's not public, so that is fine. 
     return new Person(address, age, interestList, name); 
    } 

    public PersonBuilder setAddress(
     final String newValue) 
    { 
     address = newValue; 

     return this; 
    } 

    public PersonBuilder setAge(
     final int newValue) 
    { 
     age = newValue; 

     return this; 
    } 

    public PersonBuilder setInterestList(
     final List<String> newValue) 
    { 
     interestList.clear(); 

     if (CollectionUtils.isNotEmpty(newValue)) 
     { 
      interestList.addAll(newValue); 
     } 

     return this; 
    } 

    public PersonBuilder setName(
     final String newValue) 
    { 
     name = newValue; 

     return this; 
    } 
} 


public class Person 
{ 
    private Person() 
    { 
    } 

    Person(
     final String addressValue, 
     final int ageValue, 
     final List<String> interestListValue, 
     final String name) 
    { 
     // set stuff. 
     // handle null for optional parameters. 
    } 

    // create gets or the fields, but do not create sets. Only the builder can set values in the class. 
}