CodeGym /Blog Java /Ngẫu nhiên /Tại sao chúng ta cần giao diện trong Java

Tại sao chúng ta cần giao diện trong Java

Xuất bản trong nhóm
CHÀO! Hôm nay chúng ta sẽ nói về một khái niệm quan trọng trong Java: giao diện. Từ này có lẽ đã quen thuộc với bạn. Ví dụ, hầu hết các chương trình máy tính và trò chơi đều có giao diện. Theo nghĩa rộng, giao diện là một loại 'điều khiển từ xa' kết nối hai bên tương tác. Một ví dụ đơn giản về giao diện trong cuộc sống hàng ngày là điều khiển từ xa của TV. Nó kết nối hai đối tượng — một người và TV — và thực hiện các tác vụ khác nhau: tăng hoặc giảm âm lượng, chuyển kênh và bật hoặc tắt TV. Một bên (người) cần truy cập giao diện (nhấn một nút trên điều khiển từ xa) để khiến bên thứ hai thực hiện hành động. Ví dụ: để chuyển TV sang kênh tiếp theo. Hơn nữa, người dùng không không cần biết TV được tổ chức như thế nào hoặc quá trình thay đổi kênh được thực hiện như thế nào trong nội bộ. Điều duy nhất người dùng có quyền truy cập là giao diện. Mục tiêu chính là để có được kết quả mong muốn. Điều này có liên quan gì đến lập trình và Java? Mọi thứ :) Tạo một giao diện rất giống với việc tạo một lớp thông thường, nhưng thay vào đó hãy sử dụng từclass , chúng tôi chỉ ra giao diện từ . Hãy xem giao diện Java đơn giản nhất, xem nó hoạt động như thế nào và tại sao chúng ta cần nó:

public interface CanSwim {

     public void swim();
}
Chúng tôi đã tạo giao diện CanSwim . Nó hơi giống điều khiển từ xa của chúng ta, nhưng với một 'nút': phương thức bơi() . Nhưng làm thế nào để chúng ta sử dụng bộ điều khiển từ xa này? Để làm điều này, chúng ta cần triển khai một phương thức, tức là nút điều khiển từ xa của chúng ta. Để sử dụng một giao diện, một số lớp trong chương trình của chúng ta phải triển khai các phương thức của nó. Hãy tạo ra một lớp có các đối tượng 'có thể bơi'. Ví dụ: một lớp Vịt phù hợp:

public class Duck implements CanSwim {

    public void swim() {
        System.out.println("Duck, swim!");
    }

    public static void main(String[] args) {

        Duck duck = new Duck();
        duck.swim();
    }
}
"Chúng ta thấy gì ở đây? Lớp Duck được 'liên kết' với giao diện CanSwim bởi từ khóa implements . Bạn có thể nhớ lại rằng chúng tôi đã sử dụng một cơ chế tương tự để liên kết hai lớp thông qua kế thừa, nhưng trong trường hợp đó, chúng tôi đã sử dụng từ mở rộng. Đối với hoàn toàn rõ ràng, chúng ta có thể dịch ' public class Duck implements CanSwim ' theo nghĩa đen là: 'The public Duck class implements the CanSwim interface'. Điều này có nghĩa là một lớp được liên kết với một giao diện phải thực hiện tất cả các phương thức của nó. Lưu ý: Ducklớp của chúng ta, giống như giao CanSwimdiện, có một swim()phương thức và nó chứa một số logic. Đây là một yêu cầu bắt buộc. Nếu chúng ta chỉ viếtpublic class Duck implements CanSwimkhông tạo swim()phương thức trong Ducklớp, trình biên dịch sẽ báo lỗi cho chúng ta: Vịt không trừu tượng và không ghi đè phương thức trừu tượng swim() trong CanSwim Tại sao? Lý do tại sao điều này xảy ra? Nếu chúng tôi giải thích lỗi bằng ví dụ về TV, nó sẽ giống như đưa cho ai đó một chiếc điều khiển từ xa của TV có nút 'chuyển kênh' không thể chuyển kênh. Bạn có thể nhấn nút bao nhiêu tùy thích, nhưng nó sẽ không hoạt động. Điều khiển từ xa không tự thay đổi kênh: nó chỉ gửi tín hiệu đến TV, tín hiệu này sẽ thực hiện quy trình chuyển kênh phức tạp. Và với con vịt của chúng ta cũng vậy: nó phải biết bơi để có thể được gọi bằng CanSwimgiao diện. Nếu nó không biết làm thế nào,CanSwimgiao diện không kết nối hai bên - người và chương trình. Người đó sẽ không thể sử dụng swim()phương pháp này để bơi Ducktrong chương trình. Bây giờ bạn đã hiểu rõ ràng hơn về giao diện dùng để làm gì. Một giao diện mô tả hành vi mà các lớp thực hiện giao diện phải có. 'Hành vi' là một tập hợp các phương pháp. Nếu chúng ta muốn tạo một số trình nhắn tin, cách dễ nhất là tạo một Messengergiao diện. Mọi sứ giả cần gì? Ở mức cơ bản, họ phải có khả năng nhận và gửi tin nhắn.

public interface Messenger{

     public void sendMessage();

     public void getMessage();
}
Bây giờ chúng ta có thể chỉ cần tạo các lớp messenger triển khai giao diện tương ứng. Bản thân trình biên dịch sẽ 'buộc' chúng ta triển khai chúng trong các lớp của mình. điện báo:

public class Telegram implements Messenger {

    public void sendMessage() {

        System.out.println("Sending a Telegram message!");
    }

     public void getMessage() {
         System.out.println("Receiving a Telegram message!");
     }
}
WhatsApp:

public class WhatsApp implements Messenger {

    public void sendMessage() {

        System.out.println("Sending a WhatsApp message!");
    }

     public void getMessage() {
         System.out.println("Reading a WhatsApp message!");
     }
}
Viber:

public class Viber implements Messenger {

    public void sendMessage() {

        System.out.println("Sending a Viber message!");
    }

     public void getMessage() {
         System.out.println("Receiving a Viber message!");
     }
}
Điều này mang lại những lợi ích gì? Điều quan trọng nhất trong số đó là khớp nối lỏng lẻo. Hãy tưởng tượng rằng chúng tôi đang thiết kế một chương trình sẽ thu thập dữ liệu khách hàng. Lớp Clientchắc chắn cần một trường để cho biết ứng dụng khách đang sử dụng trình nhắn tin cụ thể nào. Không có giao diện, điều này sẽ trông kỳ lạ:

public class Client {

    private WhatsApp whatsApp;
    private Telegram telegram;
    private Viber viber;
}
Chúng tôi đã tạo ba trường, nhưng khách hàng chỉ có thể có một trình nhắn tin. Chúng tôi chỉ không biết cái nào. Vì vậy, chúng tôi phải thêm mọi khả năng vào lớp để có thể giao tiếp với khách hàng. Nó chỉ ra rằng một hoặc hai trong số chúng sẽ luôn luôn null, chương trình hoàn toàn không cần thiết. Thay vào đó, tốt hơn là sử dụng giao diện của chúng tôi:

public class Client {

    private Messenger messenger;
}
Đây là một ví dụ về khớp nối lỏng lẻo! Thay vì chỉ định một lớp trình nhắn tin cụ thể trong Clientlớp, chúng tôi chỉ chỉ ra rằng máy khách có một trình nhắn tin. Cái nào chính xác sẽ được xác định trong khi chương trình chạy. Nhưng tại sao chúng ta cần giao diện cho việc này? Tại sao chúng thậm chí còn được thêm vào ngôn ngữ? Đó là một câu hỏi hay - và câu hỏi đúng! Chúng ta không thể đạt được kết quả tương tự bằng cách sử dụng thừa kế thông thường? Lớp Messengervới tư cách là cha, và Viber, Telegram, và WhatsAppvới tư cách là con. Thật vậy, điều đó là có thể. Nhưng có một trở ngại. Như bạn đã biết, Java không có đa kế thừa. Nhưng có hỗ trợ cho nhiều giao diện. Một lớp có thể triển khai bao nhiêu giao diện tùy thích. Hãy tưởng tượng rằng chúng ta có một Smartphonelớp có mộtApptrường, đại diện cho một ứng dụng được cài đặt trên điện thoại thông minh.

public class Smartphone {

    private App app;
}
Tất nhiên, một ứng dụng và một trình nhắn tin là tương tự nhau, nhưng chúng vẫn là những thứ khác nhau. Có thể có phiên bản dành cho thiết bị di động và máy tính để bàn của trình nhắn tin, nhưng Ứng dụng đại diện cụ thể cho ứng dụng dành cho thiết bị di động. Đây là thỏa thuận - nếu chúng ta sử dụng tính kế thừa, chúng ta sẽ không thể thêm một Telegramđối tượng vào Smartphonelớp. Xét cho cùng, Telegramlớp không thể đồng thời kế thừa AppMessenger! Và chúng tôi đã làm cho nó kế thừa Messengervà thêm nó vào Clientlớp. Nhưng Telegramlớp có thể dễ dàng thực hiện cả hai giao diện! Theo đó, chúng ta có thể cung cấp cho Clientlớp một Telegramđối tượng dưới dạng a Messengervà chúng ta có thể cung cấp cho lớp Smartphonedưới dạng đối tượng App. Đây là cách bạn làm điều đó:

public class Telegram implements Application, Messenger {

    // ...methods
}

public class Client {

    private Messenger messenger;

    public Client() {
        this.messenger = new Telegram();
    }
}


public class Smartphone {

    private Application application;

    public Smartphone() {
        this.application = new Telegram();
    }
}
Bây giờ chúng tôi đang sử dụng Telegramlớp theo cách chúng tôi muốn. Ở một số nơi, nó hoạt động như một tệp App. Ở những nơi khác, nó hoạt động như một tệp Messenger. Bạn chắc chắn đã nhận thấy rằng các phương thức giao diện luôn 'trống rỗng', tức là chúng không có triển khai. Lý do cho điều này rất đơn giản: giao diện mô tả hành vi, nhưng nó không thực hiện hành vi đó. 'Tất cả các đối tượng triển khai CanSwimgiao diện phải biết bơi': đó là tất cả những gì giao diện cho chúng ta biết. Cách cụ thể mà cá, vịt và ngựa bơi là một câu hỏi cho Fish, Duck, vàHorsecác lớp, không phải giao diện. Giống như thay đổi kênh là một nhiệm vụ cho TV. Điều khiển từ xa chỉ cung cấp cho bạn một nút cho việc này. Tuy nhiên, một bổ sung thú vị đã xuất hiện trong Java 8 - các phương thức mặc định. Ví dụ, giao diện của bạn có 10 phương thức. 9 trong số chúng có cách triển khai khác nhau trong các lớp khác nhau, nhưng một lớp được triển khai giống nhau cho tất cả. Trước đây, trước Java 8, các phương thức giao diện không có bất kỳ triển khai nào: trình biên dịch ngay lập tức báo lỗi. Bây giờ bạn có thể làm một cái gì đó như thế này:

public interface CanSwim {

   public default void swim() {
       System.out.println("Swim!");
   }

   public void eat();

   public void run();
}
Sử dụng defaulttừ khóa, chúng tôi đã tạo một phương thức giao diện với cách triển khai mặc định. Chúng tôi cần cung cấp triển khai của riêng mình cho hai phương thức khác — eat()run()— trong tất cả các lớp triển khai CanSwim. Chúng ta không cần phải làm điều này với swim()phương thức: việc triển khai sẽ giống nhau ở mọi lớp. Nhân tiện, bạn đã bắt gặp các giao diện trong các tác vụ trước đây, ngay cả khi bạn không để ý :) Đây là một ví dụ sinh động: Tại sao giao diện lại cần thiết trong Java - 2Bạn đã làm việc với các giao diện ListSet! Chính xác hơn, bạn đã làm việc với các triển khai của chúng — ArrayList, LinkedList, HashSetv.v. Biểu đồ tương tự đưa ra một ví dụ rõ ràng trong đó một lớp triển khai nhiều giao diện cùng một lúc. Ví dụ, LinkedListthực hiện các ListDequegiao diện (hàng đợi kết thúc kép). Bạn đã quen thuộc với Mapgiao diện, hay đúng hơn là với HashMapcách triển khai của nó. Nhân tiện, sơ đồ này minh họa một tính năng: các giao diện có thể kế thừa các giao diện khác. Giao SortedMapdiện kế thừa Map, trong khi Dequekế thừa Queue. Điều này là cần thiết nếu bạn muốn hiển thị mối quan hệ giữa các giao diện, trong đó một giao diện là phiên bản mở rộng của giao diện khác. Hãy xem xét một ví dụ với Queuegiao diện. Chúng tôi chưa xem xétQueues, nhưng nó khá đơn giản và hoạt động giống như một hàng đợi hoặc hàng thông thường tại một cửa hàng. Bạn chỉ có thể thêm các mục vào cuối hàng đợi và chỉ có thể lấy chúng từ đầu. Tại một số thời điểm, các nhà phát triển cần một phiên bản nâng cao của hàng đợi để thêm và lấy các mục ở cả hai đầu. Vì vậy, họ đã tạo ra một Dequegiao diện, đó là một hàng đợi hai đầu. Nó có tất cả các phương thức của một hàng đợi thông thường. Xét cho cùng, nó là cha của hàng đợi hai đầu, nhưng nó cũng bổ sung các phương thức mới.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION