Java Generics - cách tạo sự cân bằng giữa tính biểu cảm và tính đơn giản


8

Tôi đang phát triển một số mã sử dụng thuốc generic và một trong những nguyên tắc hướng dẫn của tôi là làm cho nó có thể sử dụng được cho các tình huống trong tương lai, và không chỉ ngày nay. Tuy nhiên, một số đồng nghiệp đã bày tỏ rằng tôi có thể đã đánh đổi khả năng đọc vì lợi ích của khả năng mở rộng. Tôi muốn thu thập một số phản hồi về các cách có thể để giải quyết điều này.

Cụ thể, đây là một giao diện xác định một biến đổi - bạn bắt đầu với một bộ sưu tập các mục nguồn và bạn áp dụng biến đổi cho từng thành phần, lưu trữ các kết quả trong một bộ sưu tập đích. Ngoài ra, tôi muốn có thể trả lại bộ sưu tập đích cho người gọi và thay vì buộc họ sử dụng tham chiếu Bộ sưu tập, tôi muốn họ có thể sử dụng bất kỳ loại bộ sưu tập nào họ thực sự cung cấp cho bộ sưu tập đích.

Cuối cùng, tôi làm cho loại vật phẩm trong bộ sưu tập đích có thể khác với loại vật phẩm trong bộ sưu tập nguồn, bởi vì có lẽ đó là những gì biến đổi làm được. Ví dụ, trong mã của tôi, một số mục nguồn tạo nên một mục đích sau khi chuyển đổi.

Điều này mang lại giao diện sau:

interface Transform<Src, Dst> {
    <DstColl extends Collection<? super Dst>> DstColl transform(
                Collection<? extends Src> sourceCollection,
                DstColl                   destinationCollection);
}

Tôi đã cố gắng trở nên tốt đẹp và áp dụng nguyên tắc PECS của Josh Bloch (nhà sản xuất mở rộng, siêu người tiêu dùng) để đảm bảo giao diện có thể sử dụng được với các loại siêu và phụ khi thích hợp. Kết quả cuối cùng là một phần của sự quái dị.

Bây giờ, thật tuyệt nếu tôi có thể mở rộng giao diện này và chuyên môn hóa nó bằng cách nào đó. Ví dụ: nếu tôi không thực sự quan tâm đến việc chơi đẹp với các kiểu con của các mục nguồn và siêu kiểu của các mục đích, tôi có thể có:

interface SimpleTransform<Src, Dst> {
    <DstColl extends Collection<Dst>> DstColl transform(
                  Collection<Src> sourceCollection,
                  DstColl         destinationCollection);
}

Nhưng không có cách nào để làm điều đó trong Java. Tôi muốn thực hiện giao diện này một cái gì đó mà những người khác thực sự sẽ xem xét làm, trái ngược với việc sợ hãi. Tôi đã xem xét một số tùy chọn:

  • Đừng trả lại bộ sưu tập đích. Có vẻ kỳ lạ khi bạn thực hiện một biến đổi nhưng không nhận lại được gì.
  • Có một lớp trừu tượng thực hiện giao diện này, nhưng sau đó chuyển các tham số sang một thứ dễ sử dụng hơn và gọi một phương thức khác là "translateImpl ()" có chữ ký đơn giản hơn và do đó giảm bớt gánh nặng nhận thức cho người thực hiện. Nhưng thật kỳ lạ khi phải viết một lớp trừu tượng chỉ để làm cho giao diện thân thiện với người dùng.
  • Bỏ qua khả năng mở rộng và chỉ cần có giao diện đơn giản hơn. Có thể cặp đôi mà không trả lại bộ sưu tập đích. Nhưng sau đó điều đó giới hạn các lựa chọn của tôi trong tương lai.

Bạn nghĩ sao? Tôi có thiếu một cách tiếp cận tôi có thể sử dụng không?


2
Cố gắng dự đoán một kịch bản trong tương lai thay vì biết trường hợp sử dụng không phải là một ý tưởng tốt. Đây là nơi mà nguyên tắc KISS được chơi.
điệp khúc

1
Ngoài nguyên tắc KISS, còn có nguyên tắc YAGNI đề nghị bạn không nên làm điều đó.
Frank

@lorus - ý tưởng của tôi là có một giao diện "biến đổi" mà tôi có thể sử dụng cho nhiều loại biến đổi, trái ngược với một trường hợp cụ thể được điều chỉnh theo sự kết hợp cụ thể của các loại mục đích và nguồn mục tiêu. Một cách để tóm tắt bài viết của tôi là "làm thế nào để tôi giữ nó đơn giản theo cách không yêu cầu làm lại đáng kể trong tương lai". Chúng ta đang ở trong một môi trường công ty điển hình nơi thời hạn là vua và tôi không thể tái cấu trúc mọi thứ trừ khi có lý do kinh doanh hợp lý. Mối quan tâm tương tự áp dụng cho YAGNI trong bình luận của Frank.
RuslanD

Thay vì Collection<? extends Src> srcCollection, bạn nên sử dụngIterable<? extends Src>
kevin cline

Câu trả lời:


3

Bộ sưu tập Guava đã cung cấp chức năng này và nếu bạn có thể chờ lâu hơn một chút, Java 8 cũng sẽ cung cấp chức năng này :-). FWIW Tôi nghĩ rằng việc bạn sử dụng ? extends Tlà thành ngữ chính xác, bạn không thể làm gì nhiều khi Java sử dụng Kiểu xóa


Thật không may, tôi không thể thuyết phục được nhóm và quản lý của mình chuyển thẳng từ Java 6 sang Java 8 :) Trong khi đó, bạn có thể làm rõ ý của bạn về "Bộ sưu tập Guava đã cung cấp chức năng này" không? Bạn đang nói về việc chuyển đổi bộ sưu tập? Một con trỏ tới một tài nguyên có liên quan sẽ rất hữu ích. Cảm ơn!
RuslanD

Tôi bối rối không biết tại sao việc tẩy xóa lại có ý nghĩa ở đây. Bạn đang nghĩ về những gì C # x.0 gọi vào và ra các tham số chung? / Sử dụng Collection<? super Dst>sẽ là lý tưởng ở đây, nếu nó cũng không trả về cùng loại (có lẽ là cùng một tham chiếu, đó không thực sự là một ý tưởng tuyệt vời (tôi nghĩ Java SE 8 sẽ làm điều đó cho một số phương thức ( into))). Thật thú vị khi lưu ý rằng việc Collections.fillsử dụng ký tự đại diện không cần thiết để có thể biểu cảm hơn. / IIRC, Java SE 6 cuối cùng đã không được hỗ trợ trong tháng này.
Tom Hawtin - tackline


2
+1 cho Guava - thật tuyệt vời và nên là một phần của bộ công cụ dành cho nhà phát triển Java (cũng là Joda Time và Joda Money)
Gary Rowe

0

Cá nhân tôi không thấy nhiều vấn đề với cách bạn xác định giao diện của mình, tuy nhiên tôi đã quen với việc chơi với Generics và tôi có thể hiểu rằng đồng nghiệp của bạn sẽ thấy mã của bạn hơi khó hiểu nếu đó không phải là trường hợp của họ.

Nếu tôi là bạn, tôi sẽ đi với giải pháp thứ ba mà bạn nêu: làm cho mọi thứ đơn giản hơn với chi phí có khả năng phải quay lại sau. Bởi vì sau tất cả, có lẽ cuối cùng bạn sẽ không phải làm thế; hoặc cuối cùng bạn sẽ có thể sử dụng nó đầy đủ vào một lúc nào đó, nhưng không đủ để bù đắp cho những nỗ lực mà bạn đã bỏ ra để làm cho giao diện này trở nên chung chung, cũng như những nỗ lực mà đồng nghiệp của bạn có thể đã làm để che đầu nó

Một điều khác cần xem xét là mức độ thường xuyên bạn sử dụng giao diện này và mức độ phức tạp. Ngoài ra, giao diện này hữu ích như thế nào trong các dự án của bạn. Nếu nó cho phép tái sử dụng cao của một số thành phần (và không chỉ tiềm năng, mà thực tế), đó là một điều tốt. Nếu một giao diện đơn giản hơn rất nhiều hoạt động 90% thời gian, bạn có thể tự hỏi liệu 10% còn lại, một giao diện phức tạp có chứng minh được sự hữu ích hay không.

Tôi không nghĩ rằng sẽ không nên sử dụng superextendmặc dù nếu bạn có thể làm mà không có chúng vào lúc này, tôi chắc chắn rằng các đồng nghiệp của bạn sẽ không thấy chúng biến mất. Tuy nhiên, bạn có thực sự có quá nhiều tình huống cần thay đổi loại hình Collectionkhông?

Một số lời khuyên đã được đưa ra là thực sự tốt và tôi đồng ý với Frank về việc tuân thủ nguyên tắc YAGNI trừ khi bạn có lý do rất chính đáng để không làm điều đó. Bạn vẫn có thể thay đổi mã khi nhu cầu phức tạp hơn sẽ xuất hiện, nhưng nó không được sử dụng nhiều để phát triển những thứ bạn không chắc chắn sẽ sớm được sử dụng. Ngoài ra, lời khuyên của Martijn về việc sử dụng Guava là một điều cần cân nhắc nghiêm túc. Tôi chưa có cơ hội sử dụng nó, nhưng tôi đã nghe rất nhiều về nó, kể cả trong một bài thuyết trình mà mô hình Transformer đã thảo luận (bạn có thể xem trực tuyến trên InfoQ nếu bạn quan tâm).

Ở một bên, sẽ không tốt hơn trong giao diện hiện tại của bạn phải destinationCollectionlà loại Class<DstColl>?


0

Tôi thích bọc các bộ sưu tập Java. Việc đóng gói thích hợp thực sự có thể giúp đỡ. Mẫu này không cần phải liên quan dựa trên loại, nhưng rất nhiều khiến cho mỗi dòng mã không biết về loại bộ sưu tập.

Ví dụ, giả sử có một danh sách các sản phẩm. Lớp sản phẩm sẽ không thay đổi và có một vài phương thức tiện ích trong đó. Lớp danh sách sản phẩm có thể thêm một sản phẩm hoặc danh sách khác vào đó. Nếu bạn muốn chạy một phương thức trên toàn bộ danh sách, nó sẽ nằm trong lớp danh sách sản phẩm. Có một phương thức sẽ lấy một lớp danh sách bộ lọc và đưa ra một danh sách sản phẩm mới với những thứ được lọc. Các bộ lọc sẽ bị xóa khỏi bản gốc.

Có một giao diện bộ lọc quyết định nếu MỘT sản phẩm vượt qua bộ lọc. Sẽ có một lớp danh sách các bộ lọc có danh sách các bộ lọc và thực hiện giao diện bằng cách yêu cầu TẤT CẢ các bộ lọc để truyền sản phẩm cho sản phẩm đi qua.

Điều thú vị là tôi có thể xếp chồng và thay đổi nó thành một danh sách được liên kết mà không có thay đổi đáng chú ý như thế này. Nó có thể lặp và làm điều kiện rất đáng chú ý và đơn giản. Vì vậy, nó ăn mặc mã bắt buộc và thêm linh hoạt. Và được định hướng tốt.


Ừm, bạn có nhận ra rằng bạn có thể làm tất cả những điều đó trong một vài dòng mã với các bộ sưu tập, luồng và lambdas Java tiêu chuẩn không? Có một "lớp danh sách sản phẩm" đặc biệt sẽ là một sự lãng phí thời gian thực sự, thực sự khủng khiếp mà không tạo ra gì ngoài đau đầu bảo trì.
Michael Borgwardt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.