Là một giao diện được coi là 'trống rỗng' nếu nó kế thừa từ các giao diện khác?


12

Các giao diện trống thường được coi là thực hành xấu, theo như tôi có thể nói - đặc biệt là nơi những thứ như thuộc tính được hỗ trợ bởi ngôn ngữ.

Tuy nhiên, một giao diện có được coi là 'trống' nếu nó kế thừa từ các giao diện khác không?

interface I1 { ... }
interface I2 { ... } //unrelated to I1

interface I3
    : I1, I2
{
    // empty body
}

Bất cứ điều gì thực hiện I3sẽ cần phải thực hiện I1I2, và các đối tượng từ các lớp khác nhau kế thừa I3có thể được sử dụng thay thế cho nhau (xem bên dưới), vậy có đúng không khi gọi I3 trống ? Nếu vậy điều gì sẽ là một cách tốt hơn để kiến ​​trúc này?

// with I3 interface
class A : I3 { ... }
class B : I3 { ... }

class Test {
    void foo() {
        I3 something = new A();
        something = new B();
        something.SomeI1Function();
        something.SomeI2Function();
    }
}

// without I3 interface
class A : I1, I2 { ... }
class B : I1, I2 { ... }

class Test {
    void foo() {
        I1 something = new A();
        something = new B();
        something.SomeI1Function();
        something.SomeI2Function(); // we can't do this without casting first
    }
}

1
Không thể chỉ định một số giao diện là loại tham số / trường, v.v. là một lỗ hổng thiết kế trong C # / .NET.
CodeInChaos

Tôi nghĩ rằng cách tốt hơn để kiến ​​trúc phần này nên đi vào một câu hỏi riêng biệt. Ngay bây giờ câu trả lời được chấp nhận không liên quan gì đến tiêu đề câu hỏi.
Kapol

@CodesInChaos Không, không phải vì có hai cách để làm điều này. Một trong câu hỏi này, cách khác là chỉ định tham số loại chung cho đối số phương thức và sử dụng hạn chế nơi để chỉ định cả hai giao diện.
Andy

Liệu nó có ý nghĩa rằng một lớp thực hiện I1 và I2, nhưng không phải I3, không thể được sử dụng bởi foo? (Nếu câu trả lời là "có", thì hãy tiếp tục!)
user253751

@Andy Trong khi tôi không hoàn toàn đồng ý với tuyên bố của CodeInChaos rằng đó là một lỗ hổng như vậy, tôi không nghĩ rằng các tham số loại chung trên phương thức là một giải pháp - nó đẩy trách nhiệm biết một loại được sử dụng trong triển khai phương thức lên bất cứ điều gì đang gọi phương thức, mà cảm thấy sai. Có lẽ một số cú pháp như var : I1, I2 something = new A();cho các biến cục bộ sẽ rất hay (nếu chúng ta bỏ qua những điểm tốt được nêu trong câu trả lời của Konrad)
Kai

Câu trả lời:


27

Bất cứ điều gì thực hiện I3 sẽ cần phải thực hiện I1 và I2, và các đối tượng từ các lớp khác nhau kế thừa I3 sau đó có thể được sử dụng thay thế cho nhau (xem bên dưới), vậy có đúng không khi gọi I3 trống? Nếu vậy điều gì sẽ là một cách tốt hơn để kiến ​​trúc này?

"Đóng bó" I1I2vào I3Mời một đẹp (?) Phím tắt, hoặc một số loại đường cú pháp cho bất cứ nơi nào bạn muốn cả hai được thực hiện.

Vấn đề với cách tiếp cận này là bạn không thể ngăn các nhà phát triển khác triển khai rõ ràng I1 I2 sát cánh bên nhau, nhưng nó không hoạt động theo cả hai cách - trong khi : I3tương đương với : I1, I2, : I1, I2không tương đương : I3. Ngay cả khi chúng giống nhau về mặt chức năng, một lớp hỗ trợ cả hai I1I2sẽ không bị phát hiện là hỗ trợ I3.

Điều này có nghĩa là bạn đang cung cấp hai cách để hoàn thành cùng một thứ - "thủ công" và "ngọt ngào" (với đường cú pháp của bạn). Và nó có thể có tác động xấu đến tính nhất quán của mã. Cung cấp 2 cách khác nhau để thực hiện điều tương tự ở đây, ở đó và ở một nơi khác dẫn đến 8 kết hợp có thể có :)

void foo() {
    I1 something = new A();
    something = new B();
    something.SomeI1Function();
    something.SomeI2Function(); // we can't do this without casting first
}

OK, bạn không thể. Nhưng có lẽ đó thực sự là một dấu hiệu tốt?

Nếu I1I2ngụ ý các trách nhiệm khác nhau (nếu không sẽ không cần phải tách chúng ra ngay từ đầu), thì có lẽ đã foo()sai khi thử và thực somethinghiện hai trách nhiệm khác nhau trong một lần?

Nói cách khác, có thể foo()chính nó đã vi phạm nguyên tắc Trách nhiệm đơn lẻ và thực tế là nó yêu cầu kiểm tra kiểu đúc và thời gian chạy để kéo nó ra là một lá cờ đỏ báo động cho chúng ta về nó.

BIÊN TẬP:

Nếu bạn khăng khăng đảm bảo foosẽ lấy thứ gì đó thực hiện I1I2, bạn có thể chuyển chúng dưới dạng tham số riêng biệt:

void foo(I1 i1, I2 i2) 

Và họ có thể là cùng một đối tượng:

foo(something, something);

Điều gì fooquan tâm nếu họ là cùng một ví dụ hay không?

Nhưng nếu nó không quan tâm (ví dụ: vì chúng không phải là không quốc tịch) hoặc bạn chỉ nghĩ rằng điều này trông xấu, thì người ta có thể sử dụng thuốc generic thay thế:

void Foo<T>(T something) where T: I1, I2

Bây giờ các ràng buộc chung sẽ chăm sóc hiệu ứng mong muốn trong khi không gây ô nhiễm thế giới bên ngoài với bất cứ điều gì. Đây là C # thành ngữ, dựa trên kiểm tra thời gian biên dịch, và nó cảm thấy đúng hơn về tổng thể.

Tôi thấy I3 : I2, I1phần nào là một nỗ lực để mô phỏng các loại kết hợp trong một ngôn ngữ không hỗ trợ nó ra khỏi hộp (C # hoặc Java thì không, trong khi các loại kết hợp tồn tại trong các ngôn ngữ chức năng).

Cố gắng mô phỏng một cấu trúc không thực sự được hỗ trợ bởi ngôn ngữ thường rất khó hiểu. Tương tự, trong C #, bạn có thể sử dụng các phương thức và giao diện mở rộng nếu bạn muốn mô phỏng mixins . Khả thi? Đúng. Ý tưởng tốt? Tôi không chắc.


4

Nếu có một tầm quan trọng đặc biệt trong việc một lớp thực hiện cả hai giao diện, thì tôi thấy không có gì sai khi tạo giao diện thứ ba kế thừa từ cả hai. Tuy nhiên, tôi sẽ cẩn thận để không rơi vào cái bẫy của sự áp đảo. Một lớp có thể kế thừa từ cái này hay cái kia, hoặc cả hai. Trừ khi chương trình của tôi có nhiều lớp trong ba trường hợp này, thì sẽ hơi nhiều khi tạo giao diện cho cả hai, và chắc chắn là không nếu hai giao diện đó không liên quan đến nhau (không có giao diện IComparableAndSerializable).

Tôi nhắc bạn rằng bạn có thể tạo một lớp trừu tượng cũng kế thừa từ cả hai giao diện và bạn có thể sử dụng lớp trừu tượng đó thay cho I3. Tôi nghĩ sẽ là sai lầm khi nói rằng bạn không nên làm điều đó, nhưng ít nhất bạn nên có một lý do chính đáng.


Huh? Bạn chắc chắn có thể thực hiện hai giao diện trong một lớp duy nhất. Bạn không kế thừa giao diện, bạn thực hiện chúng.
Andy

@Andy Tôi đã nói rằng một lớp duy nhất không thể thực hiện hai giao diện ở đâu? Đối với những gì liên quan đến việc sử dụng từ "kế thừa" ở nơi "thực hiện", ngữ nghĩa. Trong C ++, kế thừa sẽ hoạt động hoàn toàn tốt vì thứ gần nhất với giao diện các lớp trừu tượng.
Neil

Kế thừa và thực hiện giao diện không phải là cùng một khái niệm. Các lớp kế thừa nhiều lớp con có thể dẫn đến một số vấn đề kỳ lạ, đó không phải là trường hợp thực hiện nhiều giao diện. Và C ++ thiếu giao diện không có nghĩa là sự khác biệt biến mất, điều đó có nghĩa là C ++ bị hạn chế trong những gì nó có thể làm. Kế thừa là mối quan hệ "is-a", trong khi các giao diện chỉ định "có thể làm".
Andy

@Andy Các vấn đề kỳ lạ gây ra bởi sự va chạm giữa các phương thức được triển khai trong các lớp đã nói, vâng. Tuy nhiên, đó không còn là một giao diện không có tên cũng như trong thực tế. Các lớp trừu tượng khai báo nhưng không thực hiện các phương thức theo nghĩa của từ trừ tên, giao diện. Thuật ngữ thay đổi giữa các ngôn ngữ, nhưng điều đó không có nghĩa là một tham chiếu và một con trỏ không cùng một khái niệm. Đó là một điểm phạm vi, nhưng nếu bạn nhấn mạnh vào điều này, thì chúng tôi sẽ phải đồng ý không đồng ý.
Neil

"Thuật ngữ thay đổi giữa các ngôn ngữ" Không, không. Thuật ngữ OO phù hợp giữa các ngôn ngữ; Tôi chưa bao giờ nghe một lớp trừu tượng có nghĩa là bất cứ điều gì khác trong mỗi ngôn ngữ OO tôi đã sử dụng. Vâng, bạn có thể có ngữ nghĩa của một giao diện trong C ++, nhưng vấn đề là không có gì ngăn cản ai đó đi và thêm hành vi vào một lớp trừu tượng trong ngôn ngữ đó và phá vỡ các ngữ nghĩa đó.
Andy

2

Giao diện trống thường được coi là thực hành xấu

Tôi không đồng ý. Đọc về giao diện đánh dấu .

Trong khi một giao diện điển hình chỉ định chức năng (dưới dạng khai báo phương thức) mà một lớp triển khai phải hỗ trợ, giao diện đánh dấu không cần phải làm như vậy. Sự hiện diện đơn thuần của một giao diện như vậy chỉ ra hành vi cụ thể trên một phần của lớp thực hiện.


Tôi tưởng tượng OP biết về họ, nhưng thực ra họ có một chút tranh cãi. Mặc dù một số tác giả đề xuất chúng (ví dụ Josh Bloch trong "Java hiệu quả"), một số tác giả cho rằng chúng là một phản mẫu và di sản từ thời mà Java chưa có chú thích. (Các chú thích trong Java gần tương đương với các thuộc tính trong .NET). Ấn tượng của tôi là chúng phổ biến hơn nhiều trong thế giới Java so với .NET.
Konrad Morawski

1
Thỉnh thoảng tôi sử dụng chúng, nhưng cảm giác hơi khó chịu - nhược điểm, ngoài đầu tôi, là bạn không thể giúp thực tế họ được thừa hưởng, vì vậy một khi bạn đánh dấu một lớp với một đứa trẻ, tất cả trẻ em của nó đều nhận được đánh dấu là tốt cho dù bạn muốn hay không. Và họ dường như khuyến khích thực hành xấu sử dụng instanceofthay vì đa hình
Konrad Morawski
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.