Có phải là cách thực hành tốt để triển khai hai phương thức mặc định Java 8 về mặt nhau không?


14

Tôi đang thiết kế một giao diện với hai phương thức liên quan, tương tự như sau:

public interface ThingComputer {
    default Thing computeFirstThing() {
        return computeAllThings().get(0);
    }

    default List<Thing> computeAllThings() {
        return ImmutableList.of(computeFirstThing());
    }
}

Khoảng một nửa các triển khai sẽ chỉ tính toán một điều, trong khi nửa còn lại có thể tính toán nhiều hơn.

Điều này có bất kỳ tiền lệ nào trong mã Java 8 được sử dụng rộng rãi không? Tôi biết Haskell làm những điều tương tự trong một số kiểu chữ ( Eqví dụ).

Ưu điểm là tôi phải viết mã ít hơn đáng kể so với nếu tôi có hai lớp trừu tượng ( SingleThingComputerMultipleThingComputer).

Nhược điểm là một triển khai trống biên dịch nhưng sẽ nổ tung khi chạy với a StackOverflowError. Có thể phát hiện đệ quy lẫn nhau bằng một ThreadLocalvà đưa ra một lỗi đẹp hơn, nhưng điều đó làm tăng thêm chi phí cho mã không có lỗi.


3
Tại sao bạn lại cố tình viết mã đảm bảo có đệ quy vô hạn? Không có gì tốt có thể đến từ đó.

1
Vâng, nó không "được bảo đảm"; việc thực hiện đúng sẽ ghi đè lên một trong những phương pháp đó.
Tavian Barnes

6
Bạn không biết điều đó. Một lớp hoặc giao diện cần tự đứng vững và không cho rằng một lớp con sẽ thực hiện mọi thứ theo một cách nhất định để tránh các lỗi logic lớn. Mặt khác, nó giòn và không thể sống theo sự đảm bảo của nó. Lưu ý rằng tôi không nói rằng giao diện của bạn có thể hoặc nên thực thi một lớp triển khai đang làm throw new Error();hoặc một cái gì đó ngu ngốc, chỉ là giao diện đó không nên có một hợp đồng dễ vỡ thông qua defaultcác phương thức.

Tôi đồng ý với @Snowman, đó là một quả mìn. Tôi nghĩ đó là "mã ác" trong sách giáo khoa theo nghĩa mà Jon Skeet muốn nói - lưu ý rằng cái ác không giống như xấu , nó xấu xa / thông minh / cám dỗ, nhưng vẫn sai :) Tôi khuyên bạn nên youtu.be/lGbQiguuUGc?t=15m26s ( hoặc thực sự, toàn bộ cuộc nói chuyện cho bối cảnh rộng hơn - nó rất thú vị).
Konrad Morawski

1
Chỉ vì mỗi chức năng có thể được quy định tại các điều khoản của bên kia không không có nghĩa là cả hai đều có thể được quy định tại các điều khoản của nhau. Điều đó không bay trong bất kỳ ngôn ngữ. Bạn muốn có một giao diện với 2 người thừa kế trừu tượng, mỗi người thực hiện một cái theo nghĩa khác nhưng trừu tượng hóa cái kia để người triển khai chọn phía nào họ muốn thực hiện bằng cách kế thừa từ lớp trừu tượng chính xác. Một bên phải được xác định độc lập với bên kia, nếu không thì pop đi theo ngăn xếp . Bạn có mặc định điên rồ cho rằng những người triển khai sẽ giải quyết vấn đề cho bạn, abstracttồn tại để buộc họ giải quyết nó.
Jimmy Hoffa

Câu trả lời:


16

TL; DR: không làm điều này.

Những gì bạn hiển thị ở đây là mã giòn.

Một giao diện là một hợp đồng. Nó nói "bất kể đối tượng bạn nhận được là gì, nó có thể làm X và Y." Vì nó được viết, giao diện của bạn không X cũng như Y vì nó được đảm bảo gây ra lỗi tràn ngăn xếp.

Ném một lỗi hoặc lớp con cho thấy một lỗi nghiêm trọng không nên bắt gặp:

Lỗi là một lớp con của throwable chỉ ra các vấn đề nghiêm trọng mà ứng dụng hợp lý không nên cố gắng nắm bắt.

Hơn nữa, VirtualMachineError , lớp cha của StackOverflowError , nói điều này:

Ném để chỉ ra rằng Máy ảo Java bị hỏng hoặc đã hết tài nguyên cần thiết để nó tiếp tục hoạt động.

Chương trình của bạn không nên quan tâm với nguồn JVM . Đó là công việc của JVM. Tạo một chương trình gây ra lỗi JVM là một phần của hoạt động bình thường là không tốt. Nó đảm bảo chương trình của bạn sẽ bị sập hoặc buộc người dùng giao diện này mắc các lỗi mà nó không nên quan tâm.


Bạn có thể biết Eric Lippert từ những nỗ lực như là danh dự "thành viên của ủy ban thiết kế ngôn ngữ C #." Anh ấy nói về các tính năng ngôn ngữ thúc đẩy mọi người hướng tới thành công hay thất bại: mặc dù đây không phải là một tính năng ngôn ngữ hoặc một phần của thiết kế ngôn ngữ, quan điểm của anh ấy cũng có giá trị như nhau khi thực hiện giao diện hoặc sử dụng các đối tượng.

Bạn có nhớ trong Cô dâu công chúa khi Westley thức dậy và thấy mình bị nhốt trong Hố tuyệt vọng với một người bạch tạng khàn khàn và người đàn ông sáu ngón độc ác, Bá tước Rugen? Ý tưởng nguyên tắc của một hố sâu tuyệt vọng có hai mặt. Đầu tiên, đó là một cái hố, và do đó dễ rơi vào nhưng công việc khó khăn để trèo ra. Và thứ hai, nó gây ra sự tuyệt vọng. Do đó tên.

Nguồn: C ++ và hố tuyệt vọng

Có một giao diện ném StackOverflowErrortheo mặc định đẩy các nhà phát triển vào Hố tuyệt vọng và đó là ý tưởng tồi . Thay vào đó, đẩy các nhà phát triển về phía Hố thành công . Giúp dễ dàng sử dụng giao diện của bạn một cách chính xác và không bị sập JVM.

Ủy quyền giữa các phương pháp là tốt ở đây. Tuy nhiên, sự phụ thuộc nên đi một chiều. Tôi thích nghĩ về phương pháp ủy quyền như một biểu đồ chỉ đạo . Mỗi phương thức là một nút trên biểu đồ. Mỗi khi một phương thức gọi một phương thức khác, hãy vẽ một cạnh từ phương thức gọi đến phương thức được gọi.

Nếu bạn vẽ một biểu đồ và nhận thấy nó có chu kỳ, đó là mùi mã. Đó là một tiềm năng để thúc đẩy các nhà phát triển trong Hố tuyệt vọng. Lưu ý rằng nó không nên bị cấm phân loại, chỉ có điều người ta phải thận trọng . Các thuật toán đệ quy cụ thể sẽ có các chu kỳ trong biểu đồ cuộc gọi: điều đó là tốt. Tài liệu đó và cảnh báo các nhà phát triển. Nếu nó không được đệ quy, hãy thử phá vỡ chu trình đó. Nếu bạn không thể, hãy tìm hiểu những gì đầu vào có thể gây ra tràn ngăn xếp và giảm thiểu chúng hoặc ghi lại nó như là trường hợp cuối cùng nếu không có gì khác sẽ hoạt động.


Câu trả lời chính xác. Tôi tò mò tại sao đây là một thực tế phổ biến ở Haskell. Văn hóa dev khác nhau tôi đoán.
Tavian Barnes

Tôi không có kinh nghiệm về Haskell và không thể nói chuyện nếu hoặc tại sao điều này phổ biến hơn trong lập trình chức năng.

Nhìn vào nó nhiều hơn một chút, có vẻ như cộng đồng Haskell cũng không thực sự hài lòng với nó. Ngoài ra, trình biên dịch gần đây đã học cách kiểm tra "các định nghĩa hoàn chỉnh tối thiểu", do đó, một triển khai trống ít nhất sẽ tạo ra một cảnh báo.
Tavian Barnes

1
Hầu hết các ngôn ngữ lập trình chức năng có tối ưu hóa cuộc gọi đuôi. Với tối ưu hóa này, đệ quy đuôi sẽ không thổi tung ngăn xếp, do đó những thực hành như vậy có ý nghĩa.
Jason Yeo
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.