2016-04-21 22 views
5

Tôi biết rằng đã có những câu hỏi tương tự. Tôi chưa thấy câu trả lời cho câu hỏi của tôi.Trình tạo thông thạo chung trong Java

Tôi sẽ trình bày những gì tôi muốn với một số mã được đơn giản hóa. Giả sử tôi có một đối tượng phức tạp, một số giá trị của nó là chung:

public static class SomeObject<T, S> { 
    public int number; 
    public T singleGeneric; 
    public List<S> listGeneric; 

    public SomeObject(int number, T singleGeneric, List<S> listGeneric) { 
     this.number = number; 
     this.singleGeneric = singleGeneric; 
     this.listGeneric = listGeneric; 
    } 
} 

Tôi muốn xây dựng nó với cú pháp Trình xây dựng thông thạo. Tôi muốn làm cho nó thanh lịch mặc dù. Tôi muốn nó hoạt động như sau:

SomeObject<String, Integer> works = new Builder() // not generic yet! 
    .withNumber(4) 

    // and only here we get "lifted"; 
    // since now it's set on the Integer type for the list 
    .withList(new ArrayList<Integer>()) 

    // and the decision to go with String type for the single value 
    // is made here: 
    .withTyped("something") 

    // we've gathered all the type info along the way 
    .create(); 

Không cảnh báo đúc không an toàn và không cần chỉ định kiểu trả trước chung (ở trên cùng, nơi Builder được xây dựng).

Thay vào đó, chúng tôi cho phép thông tin loại lưu thông một cách rõ ràng, tiếp tục xuống chuỗi - cùng với các cuộc gọi withListwithTyped.

Bây giờ, cách nào là cách thanh lịch nhất để đạt được điều đó?

Tôi biết về các thủ thuật phổ biến nhất, chẳng hạn như việc sử dụng recursive generics, nhưng tôi đã thử nghiệm nó một lúc và không thể tìm ra cách áp dụng cho trường hợp sử dụng này.

Dưới đây là giải pháp tiết kiệm chi tiết phù hợp với mọi yêu cầu, nhưng với chi phí tuyệt vời - nó giới thiệu bốn nhà xây dựng (không liên quan đến thừa kế), thể hiện bốn kết hợp có thể là TS được xác định hay không.

Nó hoạt động, nhưng đó không phải là một phiên bản để tự hào, và unmaintainable nếu chúng tôi mong đợi các thông số chung chung hơn chỉ hai.

public static class Builder { 
    private int number; 

    public Builder withNumber(int number) { 
     this.number = number; 
     return this; 
    } 

    public <T> TypedBuilder<T> withTyped(T t) { 
     return new TypedBuilder<T>() 
       .withNumber(this.number) 
       .withTyped(t); 
    } 

    public <S> TypedListBuilder<S> withList(List<S> list) { 
     return new TypedListBuilder<S>() 
       .withNumber(number) 
       .withList(list); 
    } 
} 

public static class TypedListBuilder<S> { 
    private int number; 
    private List<S> list; 

    public TypedListBuilder<S> withList(List<S> list) { 
     this.list = list; 
     return this; 
    } 

    public <T> TypedBothBuilder<T, S> withTyped(T t) { 
     return new TypedBothBuilder<T, S>() 
       .withList(list) 
       .withNumber(number) 
       .withTyped(t); 
    } 

    public TypedListBuilder<S> withNumber(int number) { 
     this.number = number; 
     return this; 
    } 
} 

public static class TypedBothBuilder<T, S> { 
    private int number; 
    private List<S> list; 
    private T typed; 

    public TypedBothBuilder<T, S> withList(List<S> list) { 
     this.list = list; 
     return this; 
    } 

    public TypedBothBuilder<T, S> withTyped(T t) { 
     this.typed = t; 
     return this; 
    } 

    public TypedBothBuilder<T, S> withNumber(int number) { 
     this.number = number; 
     return this; 
    } 

    public SomeObject<T, S> create() { 
     return new SomeObject<>(number, typed, list); 
    } 
} 

public static class TypedBuilder<T> { 
    private int number; 
    private T typed; 

    private Builder builder = new Builder(); 

    public TypedBuilder<T> withNumber(int value) { 
     this.number = value; 
     return this; 
    } 

    public TypedBuilder<T> withTyped(T t) { 
     typed = t; 
     return this; 
    } 

    public <S> TypedBothBuilder<T, S> withList(List<S> list) { 
     return new TypedBothBuilder<T, S>() 
       .withNumber(number) 
       .withTyped(typed) 
       .withList(list); 
    } 
} 

Tôi có thể áp dụng kỹ thuật thông minh hơn không?

+0

* "unmaintainable nếu chúng tôi mong đợi nhiều thông số chung hơn chỉ hai" * Nếu bạn muốn giữ lại thứ tự tùy ý (trong ví dụ của bạn, bạn có thể làm cả hai 'withTyped (...). WithList (...)' * và * 'withList (...). withTyped (...)') sau đó vấn đề trở nên thực sự khó khăn bởi vì bạn kết thúc với một cái gì đó giống như 'n!' lớp, trong đó 'n' là số tham số kiểu. Nếu bạn thực hiện một cách tiếp cận xây dựng bước truyền thống hơn thì nó đơn giản hơn một chút. – Radiodef

+1

@Radiodef Tôi nghĩ rằng 2^n lớp: tại bất kỳ điểm nào, mọi loại chung có thể ở một trong hai trạng thái: đã được xác định hoặc chưa được xác định. Nhưng có, đây là một nhược điểm nghiêm trọng của việc thực hiện "thủ công" đó. Đó là lý do tại sao tôi tự hỏi nếu một giải pháp tốt hơn tồn tại; có lẽ tận dụng các ràng buộc chung trong một số cách thông minh. –

Trả lời

5

Được rồi, do đó, cách tiếp cận bước xây dựng bước truyền thống sẽ giống như thế này.

Thật không may, bởi vì chúng tôi đang trộn các phương pháp chung chung và không chung chung, chúng tôi phải redeclare rất nhiều phương pháp. Tôi không nghĩ rằng có một cách hay về việc này.

Ý tưởng cơ bản chỉ là: xác định từng bước trên một giao diện, sau đó triển khai tất cả các bước trên lớp riêng tư. Chúng ta có thể làm điều đó với các giao diện chung bằng cách kế thừa từ các kiểu thô của chúng. Nó xấu xí, nhưng nó hoạt động.

public interface NumberStep { 
    NumberStep withNumber(int number); 
} 
public interface NeitherDoneStep extends NumberStep { 
    @Override NeitherDoneStep withNumber(int number); 
    <T> TypeDoneStep<T> withTyped(T type); 
    <S> ListDoneStep<S> withList(List<S> list); 
} 
public interface TypeDoneStep<T> extends NumberStep { 
    @Override TypeDoneStep<T> withNumber(int number); 
    TypeDoneStep<T> withTyped(T type); 
    <S> BothDoneStep<T, S> withList(List<S> list); 
} 
public interface ListDoneStep<S> extends NumberStep { 
    @Override ListDoneStep<S> withNumber(int number); 
    <T> BothDoneStep<T, S> withTyped(T type); 
    ListDoneStep<S> withList(List<S> list); 
} 
public interface BothDoneStep<T, S> extends NumberStep { 
    @Override BothDoneStep<T, S> withNumber(int number); 
    BothDoneStep<T, S> withTyped(T type); 
    BothDoneStep<T, S> withList(List<S> list); 
    SomeObject<T, S> create(); 
} 
@SuppressWarnings({"rawtypes","unchecked"}) 
private static final class BuilderImpl implements NeitherDoneStep, TypeDoneStep, ListDoneStep, BothDoneStep { 
    private final int number; 
    private final Object typed; 
    private final List list; 

    private BuilderImpl(int number, Object typed, List list) { 
     this.number = number; 
     this.typed = typed; 
     this.list = list; 
    } 

    @Override 
    public BuilderImpl withNumber(int number) { 
     return new BuilderImpl(number, this.typed, this.list); 
    } 

    @Override 
    public BuilderImpl withTyped(Object typed) { 
     // we could return 'this' at the risk of heap pollution 
     return new BuilderImpl(this.number, typed, this.list); 
    } 

    @Override 
    public BuilderImpl withList(List list) { 
     // we could return 'this' at the risk of heap pollution 
     return new BuilderImpl(this.number, this.typed, list); 
    } 

    @Override 
    public SomeObject create() { 
     return new SomeObject(number, typed, list); 
    } 
} 

// static factory 
public static NeitherDoneStep builder() { 
    return new BuilderImpl(0, null, null); 
} 

Vì chúng tôi không muốn mọi người truy cập vào việc triển khai xấu, chúng tôi làm cho nó riêng tư và làm cho mọi người trải qua phương pháp static.

Nếu nó hoạt động khá nhiều giống như ý tưởng của riêng bạn:

SomeObject<String, Integer> works = 
    SomeObject.builder() 
     .withNumber(4) 
     .withList(new ArrayList<Integer>()) 
     .withTyped("something") 
     .create(); 

// we could return 'this' at the risk of heap pollution

này là cái gì vậy? Được rồi, vì vậy có một vấn đề nói chung ở đây, và nó là như thế này:

NeitherDoneStep step = SomeObject.builder(); 
BothDoneStep<String, Integer> both = 
    step.withTyped("abc") 
     .withList(Arrays.asList(123)); 
// setting 'typed' to an Integer when 
// we already set it to a String 
step.withTyped(123); 
SomeObject<String, Integer> oops = both.create(); 

Nếu chúng ta không tạo ra các bản sao, bây giờ chúng ta sẽ phải 123 giả mạo xung quanh như một String.

(Nếu bạn chỉ sử dụng những người xây dựng như các thiết lập thông thạo các cuộc gọi, điều này không thể xảy ra.)

Mặc dù chúng tôi không cần phải tạo một bản sao cho withNumber, tôi chỉ cần đi thêm bước và làm cho người xây dựng không thay đổi được. Chúng tôi đang tạo ra nhiều đối tượng hơn chúng ta phải nhưng không thực sự là một giải pháp tốt nữa. Nếu tất cả mọi người sẽ sử dụng trình xây dựng theo cách chính xác, thì chúng tôi có thể biến nó thành có thể thay đổi và return this.


Vì chúng tôi quan tâm đến các giải pháp chung mới, đây là triển khai trình xây dựng trong một lớp duy nhất.

Sự khác biệt ở đây là chúng tôi không giữ lại các loại typedlist nếu chúng tôi gọi một trong hai người định cư của họ lần thứ hai. Điều này không thực sự là một nhược điểm cho mỗi lần, nó chỉ khác nhau tôi đoán. Nó có nghĩa là chúng ta có thể làm điều này:

SomeObject<Long, String> = 
    SomeObject.builder() 
     .withType(new Integer(1)) 
     .withList(Arrays.asList("abc","def")) 
     .withType(new Long(1L)) // <-- changing T here 
     .create(); 
public static class OneBuilder<T, S> { 
    private final int number; 
    private final T typed; 
    private final List<S> list; 

    private OneBuilder(int number, T typed, List<S> list) { 
     this.number = number; 
     this.typed = typed; 
     this.list = list; 
    } 

    public OneBuilder<T, S> withNumber(int number) { 
     return new OneBuilder<T, S>(number, this.typed, this.list); 
    } 

    public <TR> OneBuilder<TR, S> withTyped(TR typed) { 
     // we could return 'this' at the risk of heap pollution 
     return new OneBuilder<TR, S>(this.number, typed, this.list); 
    } 

    public <SR> OneBuilder<T, SR> withList(List<SR> list) { 
     // we could return 'this' at the risk of heap pollution 
     return new OneBuilder<T, SR>(this.number, this.typed, list); 
    } 

    public SomeObject<T, S> create() { 
     return new SomeObject<T, S>(number, typed, list); 
    } 
} 

// As a side note, 
// we could return e.g. <?, ?> here if we wanted to restrict 
// the return type of create() in the case that somebody 
// calls it immediately. 
// The type arguments we specify here are just whatever 
// we want create() to return before withTyped(...) and 
// withList(...) are each called at least once. 
public static OneBuilder<Object, Object> builder() { 
    return new OneBuilder<Object, Object>(0, null, null); 
} 

Cùng một điều về việc tạo bản sao và ô nhiễm heap.


Bây giờ chúng tôi đang nhận được thực sự cuốn tiểu thuyết. Ý tưởng ở đây là chúng ta có thể "vô hiệu hóa" từng phương pháp bằng cách gây ra lỗi chuyển đổi chụp.

Đó là một chút phức tạp để giải thích, nhưng ý tưởng cơ bản là:

  • Mỗi phương pháp bằng cách nào đó phụ thuộc vào một biến kiểu đó được khai báo trên lớp.
  • "Vô hiệu hóa" phương thức đó bằng cách đặt loại trả về biến kiểu thành ?.
  • Điều này gây ra lỗi chuyển đổi chụp nếu chúng tôi cố gắng gọi phương thức trên giá trị trả về đó.

Sự khác biệt giữa ví dụ này và ví dụ trước là nếu chúng ta cố gắng gọi một setter lần thứ hai, chúng tôi sẽ nhận được một lỗi biên dịch:

SomeObject<Long, String> = 
    SomeObject.builder() 
     .withType(new Integer(1)) 
     .withList(Arrays.asList("abc","def")) 
     .withType(new Long(1L)) // <-- compiler error here 
     .create(); 

Do đó, chúng tôi chỉ có thể gọi mỗi setter Một lần.

Hai nhược điểm lớn ở đây là rằng bạn:

  • không thể gọi setters lần thứ hai cho hợp pháp lý do
  • thể setters gọi lần thứ hai với null chữ.

Tôi nghĩ rằng đó là một bằng chứng khá thú vị về khái niệm, ngay cả khi đó là một chút không thực tế.

public static class OneBuilder<T, S, TCAP, SCAP> { 
    private final int number; 
    private final T typed; 
    private final List<S> list; 

    private OneBuilder(int number, T typed, List<S> list) { 
     this.number = number; 
     this.typed = typed; 
     this.list = list; 
    } 

    public OneBuilder<T, S, TCAP, SCAP> withNumber(int number) { 
     return new OneBuilder<T, S, TCAP, SCAP>(number, this.typed, this.list); 
    } 

    public <TR extends TCAP> OneBuilder<TR, S, ?, SCAP> withTyped(TR typed) { 
     // we could return 'this' at the risk of heap pollution 
     return new OneBuilder<TR, S, TCAP, SCAP>(this.number, typed, this.list); 
    } 

    public <SR extends SCAP> OneBuilder<T, SR, TCAP, ?> withList(List<SR> list) { 
     // we could return 'this' at the risk of heap pollution 
     return new OneBuilder<T, SR, TCAP, SCAP>(this.number, this.typed, list); 
    } 

    public SomeObject<T, S> create() { 
     return new SomeObject<T, S>(number, typed, list); 
    } 
} 

// Same thing as the previous example, 
// we could return <?, ?, Object, Object> if we wanted 
// to restrict the return type of create() in the case 
// that someone called it immediately. 
// (The type arguments to TCAP and SCAP should stay 
// Object because they are the initial bound of TR and SR.) 
public static OneBuilder<Object, Object, Object, Object> builder() { 
    return new OneBuilder<Object, Object, Object, Object>(0, null, null); 
} 

Một lần nữa, điều tương tự về việc tạo bản sao và ô nhiễm heap.


Dù sao, tôi hy vọng điều này sẽ cung cấp cho bạn một số ý tưởng để đánh răng vào. :)

Nếu bạn thường quan tâm đến điều này, tôi khuyên bạn nên học code generation with annotation processing, bởi vì bạn có thể tạo ra những thứ như thế này dễ dàng hơn nhiều so với viết chúng bằng tay. Như chúng ta đã nói trong phần bình luận, việc viết những thứ như thế này bằng tay trở nên không thực tế một cách nhanh chóng.

+0

Vô hiệu hóa các trình cài đặt khá thú vị, chưa từng thấy trước đây! – AdamSkywalker

+0

Đó là câu trả lời toàn diện hơn tôi mong đợi! Rất sâu sắc. Cảm ơn rất nhiều. –

Các vấn đề liên quan