Lý do của việc sử dụng giao diện so với loại bị hạn chế chung là gì


15

Trong các ngôn ngữ hướng đối tượng hỗ trợ các tham số loại chung (còn được gọi là mẫu lớp và đa hình tham số, mặc dù tất nhiên mỗi tên mang ý nghĩa khác nhau), thường có thể chỉ định ràng buộc kiểu trên tham số loại, do đó nó bị giảm từ loại khác. Ví dụ: đây là cú pháp trong C #:

//for classes:
class ExampleClass<T> where T : I1 {

}
//for methods:
S ExampleMethod<S>(S value) where S : I2 {
        ...
}

Các lý do để sử dụng các loại giao diện thực tế trên các loại bị ràng buộc bởi các giao diện đó là gì? Ví dụ, những lý do để làm cho chữ ký phương thức là I2 ExampleMethod(I2 value)gì?


4
các mẫu lớp (C ++) là một cái gì đó hoàn toàn khác biệt và mạnh mẽ hơn nhiều so với thuốc generic. Mặc dù các ngôn ngữ có chung chung đã mượn cú pháp mẫu cho chúng.
Ded repeatator

Các phương thức giao diện là các cuộc gọi gián tiếp, trong khi các phương thức kiểu có thể là các cuộc gọi trực tiếp. Vì vậy, cái sau có thể nhanh hơn cái trước và trong trường hợp reftham số loại giá trị, thực sự có thể sửa đổi loại giá trị.
dùng541686

@Ded repeatator: Xem xét rằng generic là cũ hơn các mẫu, tôi không thấy làm thế nào generic có thể mượn bất cứ thứ gì từ các mẫu, cú pháp hay cách khác.
Jörg W Mittag

3
@ JörgWMittag: Tôi nghi ngờ rằng bởi "các ngôn ngữ hướng đối tượng hỗ trợ khái quát", Ded repeatator có thể đã hiểu "Java và C #" thay vì "ML và Ada". Sau đó, ảnh hưởng từ C ++ đối với trước đây là rõ ràng, mặc dù không phải tất cả các ngôn ngữ có tính tổng quát hoặc đa hình tham số đều mượn từ C ++.
Steve Jessop

2
@SteveJessop: ML, Ada, Eiffel, Haskell có trước các mẫu C ++, Scala, F #, OCaml xuất hiện và không ai trong số họ chia sẻ cú pháp của C ++. (Thật thú vị, ngay cả D, vốn vay mượn rất nhiều từ C ++, đặc biệt là các mẫu, không chia sẻ cú pháp của C ++.) "Java và C #" là một quan điểm khá hẹp về "ngôn ngữ có chung chung", tôi nghĩ vậy.
Jörg W Mittag

Câu trả lời:


21

Sử dụng phiên bản tham số cho

  1. Thêm thông tin cho người dùng chức năng
  2. Hạn chế số lượng chương trình bạn có thể viết (kiểm tra lỗi miễn phí)

Như một ví dụ ngẫu nhiên, giả sử chúng ta có một phương pháp tính toán các gốc của phương trình bậc hai

int solve(int a, int b, int c) {
  // My 7th grade math teacher is laughing somewhere
}

Và sau đó bạn muốn nó hoạt động trên các loại số khác như những thứ bên cạnh int. Bạn có thể viết một cái gì đó như

Num solve(Num a, Num b, Num c){
  ...
}

Vấn đề là điều này không nói lên những gì bạn muốn. Nó nói rằng

Đưa cho tôi 3 thứ giống như số (không nhất thiết phải theo cùng một cách) và tôi sẽ trả lại cho bạn một số loại

Chúng ta không thể làm một cái gì đó như int sol = solve(a, b, c)nếu a, bcintvì chúng ta không biết rằng phương pháp intcuối cùng sẽ trả về ! Điều này dẫn đến một số điệu nhảy lúng túng với việc hạ thấp và cầu nguyện nếu chúng ta muốn sử dụng giải pháp trong một biểu thức lớn hơn.

Bên trong chức năng, ai đó có thể đưa cho chúng ta một hình nổi, một khối lớn và độ và chúng ta phải thêm và nhân chúng lại với nhau. Chúng tôi muốn từ chối một cách tĩnh tại vì các hoạt động giữa 3 lớp này sẽ rất vô nghĩa. Độ là mod 360, do đó sẽ không xảy ra trường hợp a.plus(b) = b.plus(a)và những điều tương tự sẽ xuất hiện.

Nếu chúng ta sử dụng đa hình tham số với phân nhóm, chúng ta có thể loại trừ tất cả điều này bởi vì kiểu của chúng ta thực sự nói lên ý nghĩa của chúng ta

<T : Num> T solve(T a, T b, T c)

Hoặc bằng từ "Nếu bạn cho tôi một số loại là số, tôi có thể giải phương trình với các hệ số đó".

Điều này đến ở rất nhiều nơi khác là tốt. Một nguồn tốt của ví dụ là chức năng mà trừu tượng qua một số loại container, ala reverse, sort, mapvv


8
Tóm lại, phiên bản chung đảm bảo rằng cả ba đầu vào (và đầu ra) sẽ cùng loại số.
Toán

Tuy nhiên, điều này sẽ giảm khi bạn không kiểm soát loại được đề cập (và do đó không thể thêm giao diện cho nó). Để có tính tổng quát tối đa, bạn sẽ phải chấp nhận một giao diện được tham số hóa bởi loại đối số (ví dụ Num<int>) làm đối số phụ. Bạn luôn có thể thực hiện giao diện cho bất kỳ loại nào thông qua ủy quyền. Đây thực chất là các loại lớp của Haskell, ngoại trừ việc sử dụng tẻ nhạt hơn nhiều vì bạn phải vượt qua giao diện một cách rõ ràng.
Doval

16

Các lý do để sử dụng các loại giao diện thực tế trên các loại bị ràng buộc bởi các giao diện đó là gì?

Bởi vì đó là những gì bạn cần ...

IFoo Fn(IFoo x);
T Fn<T>(T x) where T: IFoo;

là hai chữ ký quyết định khác nhau. Đầu tiên có bất kỳ loại nào thực hiện giao diện và đảm bảo duy nhất mà nó thực hiện là giá trị trả về thỏa mãn giao diện.

Loại thứ hai lấy bất kỳ loại nào thực hiện giao diện và đảm bảo rằng nó sẽ trả lại ít nhất loại đó một lần nữa (chứ không phải là thứ gì đó thỏa mãn giao diện ít hạn chế hơn).

Đôi khi, bạn muốn đảm bảo yếu hơn. Đôi khi bạn muốn cái mạnh hơn.


Bạn có thể đưa ra một ví dụ về cách bạn sẽ sử dụng phiên bản bảo hành yếu hơn không?
GregRos

4
@GregRos - Ví dụ, trong một số mã trình phân tích cú pháp tôi đã viết. Tôi đã có một hàm Orcó hai Parserđối tượng (một lớp cơ sở trừu tượng, nhưng nguyên tắc giữ) và trả về một cái mới Parser(nhưng với một kiểu khác). Người dùng cuối không nên biết hoặc quan tâm loại bê tông là gì.
Telastyn

Trong C #, tôi tưởng tượng việc trả lại một chữ T khác với chữ T được truyền vào là gần như không thể (nỗi đau phản xạ) mà không có sự ràng buộc mới cũng như làm cho sự đảm bảo mạnh mẽ của bạn trở nên vô dụng.
NtscCobalt

1
@NtscCobalt: Sẽ hữu ích hơn khi bạn kết hợp cả lập trình chung tham số và giao diện. Ví dụ, những gì LINQ làm mọi lúc (chấp nhận một IEnumerable<T>, trả lại một IEnumerable<T>cái khác , ví dụ như thực sự là một OrderedEnumerable<T>)
Ben Voigt

2

Việc sử dụng các tổng quát bị ràng buộc cho các tham số phương thức có thể cho phép một phương thức rất giống kiểu trả về của nó dựa trên điều được truyền vào. Trong .NET chúng cũng có thể có các lợi thế bổ sung. Trong số đó:

  1. Một phương thức chấp nhận chung chung bị ràng buộc như một tham số refhoặc outtham số có thể được truyền vào một biến thỏa mãn ràng buộc; ngược lại, một phương thức không chung chung với tham số loại giao diện sẽ bị giới hạn trong việc chấp nhận các biến của loại giao diện chính xác đó.

  2. Một phương thức có tham số loại chung T có thể chấp nhận các tập hợp chung của T. Một phương thức chấp nhận IList<T> where T:IAnimalsẽ có thể chấp nhận một List<SiameseCat>, nhưng một phương thức muốn một IList<Animal>sẽ không thể thực hiện được.

  3. Một ràng buộc đôi khi có thể chỉ định một giao diện theo kiểu chung, vd where T:IComparable<T>.

  4. Một cấu trúc thực hiện một giao diện có thể được giữ như một loại giá trị khi được truyền cho một phương thức chấp nhận một tham số chung bị ràng buộc, nhưng phải được đóng hộp khi được chuyển thành một loại giao diện. Điều này có thể có ảnh hưởng rất lớn đến tốc độ.

  5. Một tham số chung có thể có nhiều ràng buộc, trong khi không có cách nào khác để chỉ định tham số "một số loại thực hiện cả IFoo và IBar". Đôi khi, đây có thể là con dao hai lưỡi, vì mã đã nhận được tham số loại IFoosẽ rất khó để chuyển nó sang phương thức như vậy để mong đợi một loại chung bị ràng buộc kép, ngay cả khi ví dụ trong câu hỏi sẽ thỏa mãn tất cả các ràng buộc.

Nếu trong một tình huống cụ thể sẽ không có lợi thế nào khi sử dụng chung, thì chỉ cần chấp nhận một tham số của loại giao diện. Việc sử dụng chung sẽ buộc hệ thống loại và JITter phải làm thêm, vì vậy nếu không có lợi ích thì người ta không nên làm điều đó. Mặt khác, rất phổ biến rằng ít nhất một trong những lợi thế trên sẽ được áp dụng.

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.