Có nhiều giao diện hơn là có các phương thức đúng không


159

Vì vậy, hãy nói rằng tôi có giao diện này:

public interface IBox
{
   public void setSize(int size);
   public int getSize();
   public int getArea();
  //...and so on
}

Và tôi có một lớp thực hiện nó:

public class Rectangle implements IBox
{
   private int size;
   //Methods here
}

Nếu tôi muốn sử dụng giao diện IBox, tôi thực sự không thể tạo một phiên bản của nó, theo cách:

public static void main(String args[])
{
    Ibox myBox=new Ibox();
}

đúng? Vì vậy, tôi thực sự phải làm điều này:

public static void main(String args[])
{
    Rectangle myBox=new Rectangle();
}

Nếu đó là sự thật, thì mục đích duy nhất của các giao diện là để đảm bảo rằng lớp thực hiện một giao diện đã có các phương thức chính xác trong nó như được mô tả bởi một giao diện? Hoặc có sử dụng giao diện nào khác không?


2
Hãy nhớ rằng, các giao diện không dành riêng cho Java. Tất cả các ngôn ngữ OOP đều có chúng ở dạng này hay dạng khác, mặc dù không phải lúc nào cũng được định nghĩa rõ ràng như Java.
Herms

2
Về mặt kỹ thuật, tất cả các ngôn ngữ OOP được gõ mạnh đều có chúng ở dạng này hay dạng khác. Ngôn ngữ chưa được gõ hoặc gõ, không có khái niệm tương tự.
Jared

1
@Jared Bạn có nhầm lẫn khi gõ mạnh với gõ tĩnh và "gỡ" bằng cách gõ động không?
eljenso

Đa hình cũng có thể được thực hiện thông qua các giao diện. Kiểm tra phần cuối cùng của trang này codenuggets.com/2014/06/20/java-interface
Jeff

Câu trả lời:


143

Giao diện là một cách để làm cho mã của bạn linh hoạt hơn. Những gì bạn làm là đây:

Ibox myBox=new Rectangle();

Sau đó, sau này, nếu bạn quyết định bạn muốn sử dụng một loại hộp khác (có thể có một thư viện khác, với loại hộp tốt hơn), bạn chuyển mã của mình sang:

Ibox myBox=new OtherKindOfBox();

Khi bạn đã quen với nó, bạn sẽ thấy đó là một cách tuyệt vời (thực sự cần thiết) để làm việc.

Một lý do khác là, ví dụ, nếu bạn muốn tạo một danh sách các hộp và thực hiện một số thao tác trên mỗi hộp, nhưng bạn muốn danh sách chứa các loại hộp khác nhau. Trên mỗi hộp bạn có thể làm:

myBox.close()

(giả sử IBox có phương thức close ()) mặc dù lớp myBox thực tế thay đổi tùy thuộc vào hộp bạn đang ở trong vòng lặp.


54
Không có gì trong câu trả lời này là độc quyền cho các giao diện Java . Điều tương tự cũng áp dụng tốt cho các lớp trừu tượng, hoặc thậm chí các lớp cụ thể. Tôi mong đợi một câu trả lời tốt để đề cập đến khả năng thực hiện nhiều giao diện và khi nào / tại sao điều đó sẽ hữu ích.
Rogério

16
Làm thế nào điều này được chọn là câu trả lời? Đó là một mô tả ngắn gọn về lý do tại sao đa hình là hữu ích, nhưng như người đăng ở trên đã nói, tôi sẽ mong đợi một lời giải thích tốt hơn về nhiều giao diện và thậm chí quan trọng hơn khi sử dụng giao diện so với một lớp trừu tượng.
trevorkavanaugh

2
Điều này có rất ít liên quan đến việc giải thích các giao diện và mọi thứ phải làm với những điều cơ bản của đa hình
A-Developer-Has-No-Name

123

Điều làm cho các giao diện trở nên hữu ích không phải là "bạn có thể thay đổi ý định và sử dụng một triển khai khác sau đó và chỉ phải thay đổi một nơi mà đối tượng được tạo ra". Đó không phải là vấn đề.

Điểm thực sự đã có trong tên: họ xác định một giao diện mà bất kỳ ai cũng có thể thực hiện để sử dụng tất cả các mã hoạt động trên giao diện đó. Ví dụ tốt nhất là java.util.Collectionscung cấp tất cả các loại phương thức hữu ích chỉ hoạt động trên các giao diện, chẳng hạn như sort()hoặc reverse()cho List. Vấn đề ở đây là mã này hiện có thể được sử dụng để sắp xếp hoặc đảo ngược bất kỳ lớp nào thực hiện các Listgiao diện - không chỉ ArrayListLinkedList, mà cả các lớp mà bạn tự viết, có thể được thực hiện theo cách mà những người viết java.util.Collectionskhông bao giờ tưởng tượng được.

Theo cách tương tự, bạn có thể viết mã hoạt động trên các giao diện nổi tiếng hoặc giao diện bạn xác định và những người khác có thể sử dụng mã của bạn mà không cần phải yêu cầu bạn hỗ trợ các lớp của họ.

Một cách sử dụng giao diện phổ biến khác là cho Callbacks. Ví dụ: java.swing.table.TableCellRenderer , cho phép bạn tác động đến cách bảng Xoay hiển thị dữ liệu trong một cột nhất định. Bạn thực hiện giao diện đó, truyền một thể hiện cho JTablevà tại một số điểm trong quá trình hiển thị bảng, mã của bạn sẽ được gọi để thực hiện công việc của nó.


9
Đó là một câu trả lời khá hay, tôi thích nó khi bạn đưa ra ví dụ từ các lớp gói java ...
Owais Qureshi

1
Tôi thíchyou can write code that operates on well-known interfaces, or interfaces you define
Manish Kumar

Đợi một lát ... Điều gì làm cho giao diện hữu ích không phải là [khả năng sử dụng bất kỳ triển khai nào bạn thích], mà là [khả năng sử dụng bất kỳ triển khai nào bạn thích]? Đề cập đến trường hợp ngược lại là khá nhiều điểm tốt mặc dù.
Powerslave

4
@Powerslave: diễn giải nó giống như Điều khiến giao diện trở nên hữu ích không phải là [khả năng viết mã mà bạn chỉ phải thay đổi một dòng khi thay đổi cách viết] mà là [khả năng viết mã mà bạn thậm chí không chỉ định thực hiện tại tất cả].
Michael Borgwardt

@MichaelBorgwardt Điều đó nghe có vẻ tốt hơn. :) Cảm ơn đã làm rõ!
Powerslave

119

Một trong nhiều cách sử dụng mà tôi đã đọc là khó khăn khi không có giao diện sử dụng nhiều kế thừa trong Java:

class Animal
{
void walk() { } 
....
.... //other methods and finally
void chew() { } //concentrate on this
} 

Bây giờ, hãy tưởng tượng một trường hợp:

class Reptile extends Animal 
{ 
//reptile specific code here
} //not a problem here

nhưng,

class Bird extends Animal
{
...... //other Bird specific code
} //now Birds cannot chew so this would a problem in the sense Bird classes can also call chew() method which is unwanted

Thiết kế tốt hơn sẽ là:

class Animal
{
void walk() { } 
....
.... //other methods 
} 

Animal không có phương thức nhai () và thay vào đó được đặt trong một giao diện như:

interface Chewable {
void chew();
}

và có lớp Bò sát thực hiện điều này chứ không phải Chim (vì Chim không thể nhai):

class Reptile extends Animal implements Chewable { } 

và incase of Birds đơn giản:

class Bird extends Animal { }

6
@CHEBURASHKA Và đặt tên xấu. Nếu Reptile"nhai", hơn bản thân nó không phải là "nhai". Quy ước của (đôi khi) giao diện đặt tên Bất cứ điều gì có thể chỉ nên được áp dụng khi nó có ý nghĩa hoàn hảo. Đặt tên giao diện Predatorsẽ thích hợp hơn ở đây.
Powerslave

6
@Powerslave Đúng là imho, một loài bò sát là "có thể nhai" / "nhai". Một con diều hâu là một động vật ăn thịt nhưng vẫn không thể nhai ... chỉ cần nitpicking nhưng "nhai" có thể được định nghĩa tốt hơn trong tài liệu của giao diện.
Madmenyo

Tuyệt vời .. Giải thích rất tốt. Cảm ơn bạn..!
Guruseshe

1
Tôi không hiểu Tất cả có vẻ như là các giao diện là một cách tốt để đảm bảo bạn triển khai các phương thức giống nhau trong mỗi lớp thực hiện nó, (ví dụ, vì vậy nó ngăn tôi đưa ra lựa chọn ngu ngốc của lớp Bird với run () và lớp Dog với runn () - tất cả đều giống nhau). Nhưng bằng cách chú ý và làm cho các lớp của tôi có cùng định dạng / cấu trúc phương thức, tôi không thể đạt được điều tương tự? Thực sự có vẻ như các giao diện chỉ cần đảm bảo rằng lập trình viên không bị lãng quên. Ngoài ra, các giao diện dường như không giúp tôi tiết kiệm thời gian; Tôi vẫn cần định nghĩa phương thức trong mỗi lớp thực hiện nó.
Alex G

@AlexG - Tôi đã nói với một trong số rất nhiều anh chàng sử dụng :) Có nhiều hơn, chúng tôi đã trầy xước bề mặt để trả lời câu hỏi một cách đơn giản!
vui vẻ

47

Mục đích của giao diện là đa hình , còn gọi là thay thế kiểu . Ví dụ, đưa ra phương pháp sau:

public void scale(IBox b, int i) {
   b.setSize(b.getSize() * i);
}

Khi gọi scalephương thức, bạn có thể cung cấp bất kỳ giá trị nào thuộc loại thực hiện IBoxgiao diện. Nói cách khác, nếu RectangleSquarecả hai thực hiện IBox, bạn có thể cung cấp một Rectanglehoặc Squarebất cứ nơi nào IBoxđược mong đợi.


8
Tại sao mục đích của đa hình giao diện, nếu tôi đã có thể đạt được điều đó trong Java với phân lớp và phương thức ghi đè?
eljenso

1
Đó là điều tương tự, ngoại trừ việc các giao diện phải bỏ qua bất kỳ triển khai nào. Các lớp do đó có thể thực hiện nhiều hơn một giao diện.
Apocalisp

4
Này, tôi chưa bao giờ nói Java có bất kỳ loại toàn vẹn khái niệm nào. Thay thế loại là mục đích của tất cả các phân nhóm. Java tình cờ có nhiều hơn một cơ chế phân nhóm, không có cơ chế nào đặc biệt tốt.
Apocalisp

1
Tôi chưa bao giờ nói bất cứ điều gì về tính toàn vẹn khái niệm là tốt. Nhưng hãy tiếp tục. Nếu bạn có thể mở rộng mọi IBox bằng phương pháp của mình, thì đó có phải là một hoạt động được khai báo trên IBox: IBox.scale (int) không?
eljenso

1
Chúng tôi sẽ không muốn kết hợp Integer với IBox, đó là lý do tại sao chúng tôi không biến nó thành một phương thức trên Integer. Và số lượng các phương thức trên một giao diện được quyết định bởi tính nhất quán và sự gắn kết của sự trừu tượng mà nó thể hiện, chứ không phải nó cồng kềnh như thế nào khi thực hiện nó. Dù sao, cảm ơn câu trả lời của bạn Apo.
eljenso

33

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ã đó:

  1. 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.)
  2. Tạo một thể hiện mới của lớp có tên là "Pig"
  3. Chỉ định rằng phiên bản mới của Pig cho biến anAnimal.
  4. Gửi tin nhắn makeNoisecho lợn.
  5. 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:

  1. 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).

  2. 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.


2
Đây là văn bản câu trả lời của tôi cho một câu hỏi khác: stackoverflow.com/questions/379282/ . Nhưng, chúng là những câu trả lời liên quan.
Jared

2
Vì vậy, tôi có thể hỏi, làm thế nào một con vịt gõ ngôn ngữ phân biệt giữa Animal.water () (mà người nông dân thận trọng thường nói rằng nó bị rò rỉ) và Plant.water () mà anh ta sử dụng để tưới cây. Sự mơ hồ là kẻ thù. Bất kỳ mức độ dài nào cần thiết để vượt qua sự mơ hồ đều được IMO chấp nhận.
Bill K

1
Yep..ambiguity là tên của trò chơi với các ngôn ngữ gõ vịt. Khi làm việc chuyên nghiệp bằng ngôn ngữ gõ vịt, sẽ không có gì lạ khi thấy các thành viên (phương thức và biến) có tên có độ dài 50 - 100 ký tự.
Jared

1
Một nhược điểm lớn khác của ngôn ngữ gõ vịt là không thể thực hiện tái cấu trúc theo chương trình dựa trên phân tích tĩnh - thử hỏi hình ảnh Smalltalk cho danh sách tất cả người gọi phương thức printString của bạn ... bạn sẽ nhận được danh sách tất cả người gọi của TẤT CẢ các phương thức printString. ...
Jared

... bởi vì người gọi của ô tô # printString không thể được phân biệt theo chương trình với người gọi của NearEarthOrbit # printString.
Jared

9

Thông thường Giao diện xác định giao diện bạn nên sử dụng (như tên gọi của nó ;-)). Mẫu vật


public void foo(List l) {
   ... do something
}

Bây giờ chức năng của bạn foochấp nhận ArrayLists, LinkedLists, ... không chỉ một loại.

Điều quan trọng nhất trong Java là bạn có thể triển khai nhiều giao diện nhưng bạn chỉ có thể mở rộng MỘT lớp! Mẫu vật:


class Test extends Foo implements Comparable, Serializable, Formattable {
...
}
là có thể nhưng

class Test extends Foo, Bar, Buz {
...
}
không phải!

Mã của bạn ở trên cũng có thể là : IBox myBox = new Rectangle();. Điều quan trọng bây giờ là CHỈ myBox chứa các phương thức / trường từ IBox chứ không phải các phương thức khác (có thể hiện có) từ đó Rectangle.


1
'Danh sách' có phải là thành viên giao diện không?
Nhấp vào Upvote

1
Danh sách là một giao diện trong thư viện bộ sưu tập java.
rmeador

Danh sách là một giao diện trong thư viện Java tiêu chuẩn ( java.sun.com/javase/6/docs/api/java/util/List.html ). Anh ta chỉ sử dụng nó để minh họa quan điểm của mình.
Michael Myers

6

Tôi nghĩ bạn hiểu mọi thứ Giao diện làm, nhưng bạn chưa tưởng tượng ra các tình huống trong đó Giao diện hữu ích.

Nếu bạn đang khởi tạo, sử dụng và giải phóng tất cả một đối tượng trong phạm vi hẹp (ví dụ: trong một cuộc gọi phương thức), Giao diện không thực sự thêm bất cứ điều gì. Giống như bạn lưu ý, lớp bê tông được biết đến.

Trường hợp Giao diện hữu ích là khi một đối tượng cần được tạo một nơi và trả lại cho người gọi có thể không quan tâm đến các chi tiết triển khai. Hãy thay đổi ví dụ IBox của bạn thành Hình dạng. Bây giờ chúng ta có thể có các triển khai Hình dạng như Hình chữ nhật, Hình tròn, Tam giác, v.v., Việc triển khai các phương thức getArea () và getSize () sẽ hoàn toàn khác nhau đối với mỗi lớp cụ thể.

Bây giờ bạn có thể sử dụng một nhà máy với nhiều phương thức createShape (params) sẽ trả về một Hình dạng phù hợp tùy thuộc vào các thông số được truyền vào. Rõ ràng, nhà máy sẽ biết về loại Hình dạng nào đang được tạo, nhưng người gọi sẽ không có để quan tâm xem đó là hình tròn, hay hình vuông, v.v.

Bây giờ, hãy tưởng tượng bạn có một loạt các hoạt động bạn phải thực hiện trên hình dạng của bạn. Có thể bạn cần sắp xếp chúng theo khu vực, đặt tất cả chúng thành một kích thước mới và sau đó hiển thị chúng trong giao diện người dùng. Tất cả các Hình dạng đều được tạo bởi nhà máy và sau đó có thể được chuyển đến các lớp Sắp xếp, Trình tạo và Hiển thị rất dễ dàng. Nếu bạn cần thêm một lớp lục giác một thời gian trong tương lai, bạn không phải thay đổi bất cứ điều gì ngoài nhà máy. Không có Giao diện, việc thêm hình dạng khác trở thành một quá trình rất lộn xộn.


6

bạn có thể làm

Ibox myBox = new Rectangle();

theo cách đó bạn đang sử dụng đối tượng này như Ibox và bạn không quan tâm rằng nó thực sự Rectangle.


Điều đó có nghĩa là chúng ta có thể viết như thế này?! > Hình chữ nhật inst = new Hình chữ nhật ();
Dr.jacky

@ Mr.Hyde Nếu sau này bạn muốn thêm Squarebạn gặp sự cố .... nếu bạn cố gắng thực hiện mà không có giao diện, bạn không thể đảm bảo điều đó SquareRectanglecó các phương pháp tương tự ... điều này có thể dẫn đến một cơn ác mộng khi bạn gặp phải một cơ sở mã lớn hơn ... Hãy nhớ rằng, các giao diện xác định một mẫu.
Hẻm núi Kolob

6

TẠI SAO GIAO DỊCH ??????

Nó bắt đầu với một con chó. Đặc biệt, một con pug .

Chó pug có nhiều hành vi khác nhau:

public class Pug { 
private String name;
public Pug(String n) { name = n; } 
public String getName() { return name; }  
public String bark() { return  "Arf!"; } 
public boolean hasCurlyTail() { return true; } }

Và bạn có một Labrador, người cũng có một tập hợp các hành vi.

public class Lab { 
private String name; 
public Lab(String n) { name = n; } 
public String getName() { return name; } 
public String bark() { return "Woof!"; } 
public boolean hasCurlyTail() { return false; } }

Chúng tôi có thể làm một số pugs và phòng thí nghiệm:

Pug pug = new Pug("Spot"); 
Lab lab = new Lab("Fido");

Và chúng ta có thể viện dẫn hành vi của họ:

pug.bark() -> "Arf!" 
lab.bark() -> "Woof!" 
pug.hasCurlyTail() -> true 
lab.hasCurlyTail() -> false 
pug.getName() -> "Spot"

Giả sử tôi chạy cũi chó và tôi cần theo dõi tất cả những con chó tôi đang ở. Tôi cần lưu trữ pugs và labradors của mình trong các mảng riêng biệt :

public class Kennel { 
Pug[] pugs = new Pug[10]; 
Lab[] labs = new Lab[10];  
public void addPug(Pug p) { ... } 
public void addLab(Lab l) { ... } 
public void printDogs() { // Display names of all the dogs } }

Nhưng điều này rõ ràng là không tối ưu. Nếu tôi cũng muốn nuôi một số poodle , tôi phải thay đổi định nghĩa Cũ của tôi để thêm một mảng Poodles. Trong thực tế, tôi cần một mảng riêng cho từng loại chó.

Cái nhìn sâu sắc: cả pugs và labradors (và poodle) là những loại chó và chúng có cùng một tập hợp hành vi. Đó là, chúng ta có thể nói (với mục đích của ví dụ này) rằng tất cả các con chó có thể sủa, có tên và có thể có hoặc không có đuôi xoăn. Chúng ta có thể sử dụng một giao diện để xác định những gì tất cả những con chó có thể làm, nhưng hãy để nó tùy thuộc vào các loại chó cụ thể để thực hiện những hành vi cụ thể đó. Giao diện cho biết "đây là những điều mà tất cả các con chó có thể làm" nhưng không cho biết mỗi hành vi được thực hiện như thế nào.

public interface Dog 
{
public String bark(); 
public String getName(); 
public boolean hasCurlyTail(); }

Sau đó, tôi thay đổi một chút các lớp Pug và Lab để thực hiện các hành vi của Chó. Chúng ta có thể nói rằng Pug là Chó và Lab là chó.

public class Pug implements Dog {
// the rest is the same as before } 

public class Lab implements Dog { 
// the rest is the same as before 
}

Tôi vẫn có thể khởi tạo Pugs và Labs như trước đây, nhưng bây giờ tôi cũng có một cách mới để làm điều đó:

Dog d1 = new Pug("Spot"); 
Dog d2 = new Lab("Fido");

Điều này nói rằng d1 không chỉ là một con chó, mà cụ thể là Pug. Và d2 cũng là một Dog, cụ thể là Lab. Chúng ta có thể gọi các hành vi và chúng hoạt động như trước:

d1.bark() -> "Arf!" 
d2.bark() -> "Woof!" 
d1.hasCurlyTail() -> true 
d2.hasCurlyTail() -> false 
d1.getName() -> "Spot"

Đây là nơi mà tất cả các công việc làm thêm được đền đáp. Lớp học cũ trở nên đơn giản hơn nhiều. Tôi chỉ cần một mảng và một phương thức addDog. Cả hai sẽ làm việc với bất kỳ đối tượng nào là một con chó; đó là các đối tượng thực hiện giao diện Dog.

public class Kennel {
Dog[] dogs = new Dog[20]; 
public void addDog(Dog d) { ... } 
public void printDogs() {
// Display names of all the dogs } }

Đây là cách sử dụng nó:

Kennel k = new Kennel(); 
Dog d1 = new Pug("Spot"); 
Dog d2 = new Lab("Fido"); 
k.addDog(d1); 
k.addDog(d2); 
k.printDogs();

Tuyên bố cuối cùng sẽ hiển thị: Spot Fido

Một giao diện cung cấp cho bạn khả năng chỉ định một tập hợp các hành vi mà tất cả các lớp thực hiện giao diện sẽ chia sẻ chung. Do đó, chúng ta có thể định nghĩa các biến và bộ sưu tập (chẳng hạn như mảng) mà không cần phải biết trước loại đối tượng cụ thể nào họ sẽ giữ, chỉ có điều họ sẽ giữ các đối tượng thực hiện giao diện.


@niranjan Kurambhatti tôi có thể làm cho tất cả các lớp để mở rộng con chó nhưng vẫn tại sao giao diện ??
Jeeva

3

Một ví dụ tuyệt vời về cách các giao diện được sử dụng là trong khung Bộ sưu tập. Nếu bạn viết một hàm mà phải mất một List, sau đó nó không quan trọng nếu người dùng đi vào một Vectorhoặc một ArrayListhoặc một HashListhoặc bất cứ điều gì. Và bạn có thể chuyển nó Listcho bất kỳ chức năng nào yêu cầu một giao diện Collectionhoặc Iterablequá.

Điều này làm cho các chức năng như Collections.sort(List list)có thể, bất kể cách thức Listđược thực hiện.


3

Đây là lý do tại sao các Mô hình nhà máy và các mẫu sáng tạo khác rất phổ biến trong Java. Bạn đúng rằng nếu không có chúng, Java sẽ không cung cấp cơ chế vượt trội để dễ dàng trừu tượng hóa việc khởi tạo. Tuy nhiên, bạn vẫn có được sự trừu tượng ở mọi nơi mà bạn không tạo một đối tượng trong phương thức của mình, đó là phần lớn mã của bạn.

Bên cạnh đó, tôi thường khuyến khích mọi người không tuân theo cơ chế "IRealname" để đặt tên giao diện. Đó là một thứ Windows / COM đặt một chân vào ký hiệu tiếng Hungary và thực sự không cần thiết (Java đã được gõ mạnh và toàn bộ quan điểm của việc có các giao diện là không thể phân biệt được với các loại lớp càng nhiều càng tốt).


1
Bạn đang nhầm lẫn gõ mạnh với gõ tĩnh.
eljenso

3

Đừng quên rằng vào một ngày sau đó, bạn có thể lấy một lớp hiện có và làm cho nó thực hiện IBox, và sau đó nó sẽ có sẵn cho tất cả các mã nhận biết hộp của bạn.

Điều này trở nên rõ ràng hơn một chút nếu giao diện được đặt tên -able . ví dụ

public interface Saveable {
....

public interface Printable {
....

vv (Các kế hoạch đặt tên không phải lúc nào cũng hoạt động, ví dụ: Tôi không chắc Boxablelà phù hợp ở đây)


3

Mục đích duy nhất của các giao diện là để đảm bảo rằng lớp thực hiện một giao diện đã có các phương thức chính xác trong nó như được mô tả bởi một giao diện? Hoặc có sử dụng giao diện nào khác không?

Tôi đang cập nhật câu trả lời với các tính năng mới của giao diện, đã được giới thiệu với phiên bản java 8 .

Từ trang tài liệu oracle về tóm tắt giao diện :

Một khai báo giao diện có thể chứa

  1. chữ ký phương thức
  2. phương thức mặc định
  3. phương pháp tĩnh
  4. định nghĩa không đổi.

Các phương thức duy nhất có triển khai là các phương thức mặc định và tĩnh.

Công dụng của giao diện :

  1. Xác định hợp đồng
  2. Để liên kết các lớp không liên quan với có một khả năng (ví dụ: các lớp triển khai Serializablegiao diện có thể có hoặc không có bất kỳ mối quan hệ nào giữa chúng ngoại trừ việc thực hiện giao diện đó
  3. Để cung cấp thực hiện hoán đổi cho nhau, ví dụ như mẫu chiến lược
  4. Các phương thức mặc định cho phép bạn thêm chức năng mới vào các giao diện của thư viện và đảm bảo khả năng tương thích nhị phân với mã được viết cho các phiên bản cũ hơn của các giao diện đó
  5. Sắp xếp các phương thức của trình trợ giúp trong các thư viện của bạn bằng các phương thức tĩnh (bạn có thể giữ các phương thức tĩnh cụ thể cho một giao diện trong cùng một giao diện thay vì trong một lớp riêng biệt)

Một số câu hỏi SE liên quan liên quan đến sự khác biệt giữa lớp trừu tượnggiao diện và các trường hợp sử dụng với các ví dụ hoạt động:

Sự khác biệt giữa một giao diện và lớp trừu tượng là gì?

Làm thế nào tôi có thể giải thích sự khác biệt giữa một lớp Giao diện và lớp Trừu tượng?

Hãy xem trang tài liệu để hiểu các tính năng mới được thêm vào trong java 8: phương thức mặc định và phương thức tĩnh .


Tôi đã xóa thẻ java-8 vì câu hỏi không hỏi gì về java-8 (và thực sự đã được hỏi từ lâu trước java-8). Thẻ là cho câu hỏi, không phải cho câu trả lời.
Tagir Valeev 7/03/2016

2

Mục đích của các giao diện là trừu tượng hóa hoặc tách rời khỏi việc thực hiện.

Nếu bạn giới thiệu một sự trừu tượng trong chương trình của mình, bạn không quan tâm đến việc triển khai có thể. Bạn quan tâm đến những gì nó có thể làm và không làm như thế nào và bạn sử dụng một interfaceđể diễn đạt điều này trong Java.


Mục đích của tất cả các chương trình có cấu trúc là trừu tượng. Tại sao bạn lại nói rằng mục đích của các giao diện là trừu tượng, vì tôi có thể đạt được điều tương tự chính xác bằng cách sử dụng khái quát và thành phần lớp?
Apocalisp

1
Nếu tất cả các lập trình có cấu trúc là trừu tượng hóa (yêu cầu của bạn), thì các giao diện là trừu tượng hóa trong trừu tượng đó.
eljenso

1

Nếu bạn có CardboardBox và HtmlBox (cả hai đều triển khai IBox), bạn có thể chuyển cả hai phương thức này cho bất kỳ phương thức nào chấp nhận IBox. Mặc dù cả hai đều rất khác nhau và không hoàn toàn có thể hoán đổi cho nhau, các phương thức không quan tâm đến "mở" hoặc "thay đổi kích thước" vẫn có thể sử dụng các lớp của bạn (có lẽ vì chúng quan tâm đến việc cần bao nhiêu pixel để hiển thị thứ gì đó trên màn hình).


1

Các giao diện nơi một bào thai được thêm vào java để cho phép nhiều kế thừa. Các nhà phát triển của Java mặc dù / nhận ra rằng có nhiều kế thừa là một tính năng "nguy hiểm", đó là lý do tại sao lại nảy ra ý tưởng về giao diện.

nhiều kế thừa là nguy hiểm vì bạn có thể có một lớp như sau:


class Box{
    public int getSize(){
       return 0;
    }
    public int getArea(){
       return 1;
    }

}

class Triangle{
    public int getSize(){
       return 1;
    }
    public int getArea(){
       return 0;
    }

}

class FunckyFigure extends Box, Triable{
   // we do not implement the methods we will used the inherited ones
}

Đó sẽ là phương thức nên được gọi khi chúng ta sử dụng


   FunckyFigure.GetArea(); 

Tất cả các vấn đề được giải quyết với các giao diện, bởi vì bạn biết rằng bạn có thể mở rộng các giao diện và chúng sẽ không có các phương thức phân loại ... vì trình biên dịch rất hay và cho bạn biết nếu bạn không thực hiện một phương thức, nhưng tôi muốn nghĩ rằng đó là một tác dụng phụ của một ý tưởng thú vị hơn.


Bạn có thể muốn tạo sự khác biệt giữa kế thừa nhiều triển khai và kế thừa nhiều giao diện trong câu trả lời của mình, nếu không, nó sẽ gây nhầm lẫn.
eljenso

0

Dưới đây là sự hiểu biết của tôi về lợi thế giao diện. Đúng nếu tôi đã sai lầm. Hãy tưởng tượng chúng tôi đang phát triển HĐH và nhóm khác đang phát triển trình điều khiển cho một số thiết bị. Vì vậy, chúng tôi đã phát triển một giao diện StorageDevice. Chúng tôi có hai triển khai của nó (FDD và HDD) được cung cấp bởi nhóm các nhà phát triển khác.

Sau đó, chúng ta có một lớp OperationsSystem có thể gọi các phương thức giao diện như saveData bằng cách chỉ cần truyền một thể hiện của lớp đã triển khai giao diện StorageDevice.

Ưu điểm ở đây là chúng tôi không quan tâm đến việc triển khai giao diện. Nhóm khác sẽ thực hiện công việc bằng cách triển khai giao diện StorageDevice.

package mypack;

interface StorageDevice {
    void saveData (String data);
}


class FDD implements StorageDevice {
    public void saveData (String data) {
        System.out.println("Save to floppy drive! Data: "+data);
    }
}

class HDD implements StorageDevice {
    public void saveData (String data) {
        System.out.println("Save to hard disk drive! Data: "+data);
    }
}

class OperatingSystem {
    public String name;
    StorageDevice[] devices;
    public OperatingSystem(String name, StorageDevice[] devices) {

        this.name = name;
        this.devices = devices.clone();

        System.out.println("Running OS " + this.name);
        System.out.println("List with storage devices available:");
        for (StorageDevice s: devices) {
            System.out.println(s);
        }

    }

    public void saveSomeDataToStorageDevice (StorageDevice storage, String data) {
        storage.saveData(data);
    }
}

public class Main {

    public static void main(String[] args) {

        StorageDevice fdd0 = new FDD();
        StorageDevice hdd0 = new HDD();     
        StorageDevice[] devs = {fdd0, hdd0};        
        OperatingSystem os = new OperatingSystem("Linux", devs);
        os.saveSomeDataToStorageDevice(fdd0, "blah, blah, blah...");    
    }
}

điều tương tự có thể được thực hiện với lớp trừu tượng StorageDevice và các lớp FDD và HDD mở rộng lớp StorageDevice. nhưng nếu chúng ta sử dụng lớp trừu tượng, chúng ta không thể tận dụng nhiều kế thừa.
Vladimir Georgiev
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.