Các giao diện cho phép các ngôn ngữ gõ tĩnh để hỗ trợ đa hình. Một người theo chủ nghĩa thuần túy hướng đối tượng sẽ nhấn mạnh rằng một ngôn ngữ sẽ cung cấp sự kế thừa, đóng gói, mô đun hóa và đa hình để trở thành một ngôn ngữ hướng đối tượng đầy đủ tính năng. Trong kiểu gõ động - hoặc vịt gõ - các ngôn ngữ (như Smalltalk,) tính đa hình là tầm thường; tuy nhiên, trong các ngôn ngữ được gõ tĩnh (như Java hoặc C #,) tính đa hình không phải là tầm thường (trên thực tế, trên bề mặt, nó có vẻ mâu thuẫn với khái niệm gõ mạnh.)
Hãy để tôi chứng minh:
Trong một ngôn ngữ được gõ động (hoặc gõ vịt) (như Smalltalk), tất cả các biến là tham chiếu đến các đối tượng (không có gì và không có gì nữa.) Vì vậy, trong Smalltalk, tôi có thể làm điều này:
|anAnimal|
anAnimal := Pig new.
anAnimal makeNoise.
anAnimal := Cow new.
anAnimal makeNoise.
Mã đó:
- Khai báo một biến cục bộ được gọi là anAnimal (lưu ý rằng chúng tôi KHÔNG chỉ định TYPE của biến - tất cả các biến là tham chiếu đến một đối tượng, không hơn không kém.)
- Tạo một thể hiện mới của lớp có tên là "Pig"
- Chỉ định rằng phiên bản mới của Pig cho biến anAnimal.
- Gửi tin nhắn
makeNoise
cho lợn.
- Lặp lại toàn bộ bằng cách sử dụng một con bò, nhưng gán nó cho cùng một biến chính xác như Lợn.
Mã Java tương tự sẽ trông giống như thế này (giả định rằng Duck và Cow là các lớp con của Animal:
Animal anAnimal = new Pig();
duck.makeNoise();
anAnimal = new Cow();
cow.makeNoise();
Đó là tất cả tốt và tốt, cho đến khi chúng tôi giới thiệu lớp Rau. Rau có một số hành vi tương tự như Động vật, nhưng không phải tất cả. Ví dụ, cả Động vật và Rau có thể có thể phát triển, nhưng rõ ràng rau không gây ra tiếng ồn và động vật không thể được thu hoạch.
Trong Smalltalk, chúng ta có thể viết điều này:
|aFarmObject|
aFarmObject := Cow new.
aFarmObject grow.
aFarmObject makeNoise.
aFarmObject := Corn new.
aFarmObject grow.
aFarmObject harvest.
Điều này hoạt động hoàn toàn tốt trong Smalltalk vì nó được gõ vịt (nếu nó đi như vịt và quạ như vịt - đó là một con vịt.) Trong trường hợp này, khi một tin nhắn được gửi đến một đối tượng, việc tra cứu được thực hiện trên danh sách phương thức của người nhận và nếu tìm thấy một phương thức phù hợp, nó được gọi. Nếu không, một số loại ngoại lệ NoSuchMethodError sẽ bị ném - nhưng tất cả đều được thực hiện trong thời gian chạy.
Nhưng trong Java, một ngôn ngữ được gõ tĩnh, chúng ta có thể gán loại nào cho biến của mình? Ngô cần được thừa hưởng từ Rau, để hỗ trợ phát triển, nhưng không thể thừa hưởng từ Động vật, vì nó không gây ra tiếng ồn. Bò cần thừa kế từ Động vật để hỗ trợ makeNùa, nhưng không thể thừa kế từ Rau vì không nên thực hiện thu hoạch. Có vẻ như chúng ta cần nhiều kế thừa - khả năng kế thừa từ nhiều hơn một lớp. Nhưng điều đó hóa ra là một tính năng ngôn ngữ khá khó khăn vì tất cả các trường hợp cạnh bật lên (điều gì xảy ra khi có nhiều hơn một siêu lớp song song thực hiện cùng một phương thức?, V.v.)
Giao diện đi kèm ...
Nếu chúng ta tạo các lớp Động vật và Thực vật, với mỗi lớp có thể phát triển, chúng ta có thể tuyên bố rằng Bò của chúng ta là Động vật và Ngô của chúng ta là Rau. Chúng tôi cũng có thể tuyên bố rằng cả Động vật và Rau quả đều có thể trồng được. Điều đó cho phép chúng tôi viết điều này để phát triển mọi thứ:
List<Growable> list = new ArrayList<Growable>();
list.add(new Cow());
list.add(new Corn());
list.add(new Pig());
for(Growable g : list) {
g.grow();
}
Và nó cho phép chúng ta làm điều này, để tạo ra tiếng động vật:
List<Animal> list = new ArrayList<Animal>();
list.add(new Cow());
list.add(new Pig());
for(Animal a : list) {
a.makeNoise();
}
Lợi thế của ngôn ngữ gõ vịt là bạn có được tính đa hình thực sự tốt: tất cả một lớp phải làm để cung cấp hành vi là cung cấp phương thức. Miễn là mọi người chơi đẹp và chỉ gửi tin nhắn phù hợp với các phương thức đã xác định, tất cả đều tốt. Nhược điểm là loại lỗi dưới đây không bị bắt cho đến khi chạy:
|aFarmObject|
aFarmObject := Corn new.
aFarmObject makeNoise. // No compiler error - not checked until runtime.
Các ngôn ngữ được nhập tĩnh cung cấp "lập trình theo hợp đồng" tốt hơn nhiều vì chúng sẽ bắt gặp hai loại lỗi dưới đây tại thời điểm biên dịch:
// Compiler error: Corn cannot be cast to Animal.
Animal farmObject = new Corn();
farmObject makeNoise();
-
// Compiler error: Animal doesn't have the harvest message.
Animal farmObject = new Cow();
farmObject.harvest();
Vì vậy, .... để tóm tắt:
Giao diện triển khai cho phép bạn chỉ định loại công việc nào mà đối tượng có thể làm (tương tác) và kế thừa lớp cho phép bạn chỉ định cách mọi việc nên được thực hiện (triển khai).
Các giao diện cung cấp cho chúng ta nhiều lợi ích của đa hình "thực", mà không phải hy sinh việc kiểm tra kiểu trình biên dịch.