2017-12-04 12 views
0

最近、私はビルダーのパターンが非常に強い状況に遭遇しましたが、私はサブクラス化する必要がありました。私はいくつかの解決策を見出し、いくつかはジェネリックを提案し、他の人は通常のサブクラス化を提案した。しかし、私が見た例のどれもフィールドを持っていました。オブジェクトの構築を開始するためにも、が必要です。私はちょっとした事例を書いて、どこに詰まっているのかを説明しました。毎回、物事が間違ったクラスを返し、静的メソッドをオーバーライドできず、super()を返すことが間違ったデータ型などを返すような問題の壁にぶち込まれていました。過剰な使用以外は方法がないと感じていますジェネリック医薬品のサブクラス化と必須パラメータを含むBuilderデザインパターン?

この場合、正しい方法はありますか?

テスター

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

ビルダー・パターンの値の1つは、コンストラクター・パラメーターの束で終わらないということです。 – DwB

答えて

1

あなたはここに書いてるの:

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

は本当にあまり自然ではないので、キャストする必要はありません。私たちは、むしろのようなものを期待
:すべてStudent.Builderの実装では、あなたがやったキャストのほか

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

も、実行時に失敗する可能性があり、ノイズや文です:

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

あなたの主な問題は、間の結合であります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として持つオブジェクトを返します。 a Studentとして

Student.builder("Jack", 19, "NYU", 1)Studentであり、Personではありません。

inheritancy上の組成を好む、実装、およびクライアント側の両方からStudent.builderにキャストを避けるために:

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 s2 = Student.builder("Jack", 19, "NYU", 1) 
        .interest("Dance") 
        .address("Dormitory") 
        .build(); 

組成がジェネラル継承としてより多くのコードがありますが、コード はより堅牢で適応性があります。

あなたの実際の問題は、私が1ヶ月前に回答した別の質問に十分近いです。
The question and its answersがおすすめです。

0

背景

  1. 静的メソッドなど、いくつかの考えは、彼らはユニットテストをより困難にする 、それほど大きくはありません。
  2. ビルダーを静的でネストされたクラスとして配置することはできますが、ビルダーを使用してクラスを構築する場合は、コンストラクターを非公開にする必要があります。
  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. 
} 
関連する問題