Site logo
Authors
  • avatar Nguyễn Đức Xinh
    Name
    Nguyễn Đức Xinh
    Twitter
Published on
Published on

Interface trong Lập Trình Hướng Đối Tượng (OOP)

Trong Lập Trình Hướng Đối Tượng (OOP), khái niệm interface đóng vai trò then chốt trong việc đạt được tính trừu tượng, tính mô-đun và khả năng mở rộng. Dù bạn đang phát triển một ứng dụng nhỏ hay một hệ thống doanh nghiệp lớn, hiểu về interface là điều cần thiết để viết mã sạch, dễ bảo trì và linh hoạt.

Bài viết này sẽ đi sâu vào khái niệm interface là gì, tại sao nó quan trọng, nó khác với các khái niệm OOP khác như lAbstract Class như thế nào, và các ví dụ thực tế trong các ngôn ngữ lập trình phổ biến.

Interface là gì?

Interface trong OOP là một hợp đồng hoặc bản thiết kế định nghĩa một tập hợp các phương thức (hoặc hành vi) mà một lớp phải triển khai. Nó chỉ định những gì một lớp nên làm, nhưng không phải cách lớp đó nên làm. Nói cách khác, một Interface định nghĩa what và lớp triển khai định nghĩa how.

Về bản chất, Interface hoàn toàn trừu tượng, chúng không thể chứa bất kỳ chi tiết triển khai nào. Chúng chỉ đơn giản khai báo các phương thức, thuộc tính hoặc sự kiện phải được triển khai bởi bất kỳ lớp nào tuân thủ Interface.

Các Điểm Chính:

  1. Không Có Triển Khai - No Implementation: Interface không thể chứa thân phương thức (ngoại trừ một số ngôn ngữ như Java 8+ cho phép phương thức mặc định).
  2. Đa Kế Thừa - Multiple Implementations: Khác với các lớp chỉ có thể kế thừa từ một lớp cha trong hầu hết các ngôn ngữ, một lớp có thể triển khai nhiều interface.
  3. Phạm Vi Truy Cập - Access Modifiers: Các phương thức trong interface thường là public theo mặc định, vì chúng được thiết kế để được triển khai bởi các lớp khác.
  4. Thực Thi Hợp Đồng - Contract Enforcement: Bất kỳ lớp nào triển khai interface đều phải cung cấp các triển khai cụ thể cho tất cả các phương thức đã khai báo.

Tương Tự Thực Tế: Một interface giống như một điều khiển từ xa (interface) với các nút bấm (phương thức). Các thiết bị khác nhau (TV, máy lạnh, hệ thống âm thanh) phản ứng với cùng một nút bấm nhưng hành xử khác nhau khi một nút được nhấn.

Interface vs Abstract Class

Mặc dù cả interface và Abstract Class đều cung cấp tính trừu tượng, chúng phục vụ các mục đích khác nhau.

Tính Năng Interface Abstract Class
Triển Khai Phương Thức Không (cho đến khi có phương thức mặc định trong một số ngôn ngữ) Có thể có cả phương thức trừu tượng và cụ thể
Đa Kế Thừa Hỗ trợ Giới hạn (kế thừa đơn trong hầu hết các ngôn ngữ)
Trường Chỉ có hằng số Có thể có biến thể hiện
Trường Hợp Sử Dụng Khi các lớp khác nhau chia sẻ hành vi mà không cần kế thừa Khi các lớp chia sẻ một cơ sở chung với triển khai một phần
Có thể khởi tạo Không Có thể (trong một số ngôn ngữ)
Có hàm khởi tạo Không Có thể (trong một số ngôn ngữ)
Có thuộc tính Đa số hổ trợ(Java, TypeScript, C#, Swift) Hổ trợ
Access Modifiers Mặc định là public Có thể có các phạm vi truy cập khác nhau

Tại Sao Nên Sử Dụng Interface?

Interface là nền tảng của thiết kế phần mềm tốt, giúp thúc đẩy một số nguyên tắc thiết kế phần mềm quan trọng. Dưới đây là một số lý do chính tại sao chúng quan trọng:

  1. Tính Trừu Tượng (Abstraction): Interface cho phép bạn tập trung vào việc một đối tượng làm gì thay vì nó làm như thế nào. Điều này thúc đẩy sự phân tách rõ ràng các mối quan tâm. Cụ thể: Interface sẽ ẩn các chi tiết triển khai phức tạp khỏi người dùng.
  2. Tính Đa Hình (Polymorphism): Interface cho phép tính đa hình, cho phép các lớp khác nhau được xử lý như các thể hiện của cùng một interface. Cụ thể: Xử lý các đối tượng của các lớp khác nhau thông qua một interface chung.
  3. Giảm Sự Phụ Thuộc (Decoupling): Interface giảm sự phụ thuộc giữa các lớp, làm cho mã của bạn mô-đun hóa hơn và dễ bảo trì hơn.
  4. Tính Linh Hoạt (Flexibility): Bằng cách lập trình theo interface thay vì triển khai cụ thể, bạn có thể dễ dàng thay thế các thành phần mà không ảnh hưởng đến phần còn lại của hệ thống. Ví dụ Laravel Service container sử dụng interface để giảm sự phụ thuộc giữa các lớp.
  5. Khả Năng Tái Sử Dụng Mã (Code Reusability): Interface khuyến khích mã có thể tái sử dụng và chuẩn hóa, vì nhiều lớp có thể triển khai cùng một interface.
  6. Khả Năng Kiểm Thử (Testability): Đơn giản hóa việc kiểm thử đơn vị bằng cách cho phép các triển khai giả lập.

Ví dụ về Trừu Tượng và Giảm Sự Phụ Thuộc(Abstraction - Decoupling)

Interface cung cấp một lớp trừu tượng giúp giảm sự phụ thuộc giữa các thành phần khác nhau trong ứng dụng của bạn. Bằng cách lập trình theo interface thay vì triển khai cụ thể, bạn làm cho mã của mình linh hoạt hơn và dễ sửa đổi hơn. Xem xét ví dụ này trong Java:

// Without interface
class PaymentProcessor {
    private StripePayment paymentGateway;
    
    public void processPayment(double amount) {
        paymentGateway.charge(amount);
    }
}

// With interface
interface PaymentGateway {
    void charge(double amount);
}

class PaymentProcessor {
    private PaymentGateway paymentGateway;
    
    public void processPayment(double amount) {
        paymentGateway.charge(amount);
    }
}

Trong cách tiếp cận thứ hai, PaymentProcessor không quan tâm liệu chúng ta đang sử dụng Stripe, PayPal hay bất kỳ dịch vụ thanh toán nào khác. Nó chỉ quan tâm rằng payment gateway có thể thực hiện phương thức charge().

Ví dụ về đa hình(Polymorphism)

Interface cho phép hành vi đa hình, cho phép các lớp khác nhau cung cấp các triển khai riêng của cùng một hợp đồng. Điều này đặc biệt mạnh mẽ khi bạn cần xử lý nhiều triển khai của cùng một khái niệm.

interface Logger {
    void log(String message);
}

class ConsoleLogger implements Logger {
    public void log(String message) {
        System.out.println(message);
    }
}

class FileLogger implements Logger {
    public void log(String message) {
        // Write to file implementation
    }
}

Ví dụ về Testing and Mocking

Interface giúp mã của bạn dễ kiểm thử hơn bằng cách cho phép dễ dàng thay thế các triển khai. Trong quá trình kiểm thử, bạn có thể cung cấp các triển khai giả lập của các interface:

class MockPaymentGateway implements PaymentGateway {
    private boolean wasCharged = false;
    
    public void charge(double amount) {
        wasCharged = true;
    }
    
    public boolean paymentWasProcessed() {
        return wasCharged;
    }
}

Interface trong Các Ngôn Ngữ Lập Trình Khác Nhau

Ví Dụ Java

// Define an interface
interface Animal {
    void makeSound();
    void eat(String food);
}

// Implement the interface in a class
class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }

    @Override
    public void eat(String food) {
        System.out.println("Dog is eating " + food);
    }
}

class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow!");
    }

    @Override
    public void eat(String food) {
        System.out.println("Cat is eating " + food);
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Animal myDog = new Dog();
        Animal myCat = new Cat();

        myDog.makeSound(); // Output: Woof!
        myCat.makeSound(); // Output: Meow!
    }
}

Ví Dụ C#

// Define an interface
interface IAnimal {
    void MakeSound();
    void Eat(string food);
}

// Implement the interface in a class
class Dog : IAnimal {
    public void MakeSound() {
        Console.WriteLine("Woof!");
    }

    public void Eat(string food) {
        Console.WriteLine("Dog is eating " + food);
    }
}

class Cat : IAnimal {
    public void MakeSound() {
        Console.WriteLine("Meow!");
    }

    public void Eat(string food) {
        Console.WriteLine("Cat is eating " + food);
    }
}

// Usage
class Program {
    static void Main(string[] args) {
        IAnimal myDog = new Dog();
        IAnimal myCat = new Cat();

        myDog.MakeSound(); // Output: Woof!
        myCat.MakeSound(); // Output: Meow!
    }
}

Ví Dụ TypeScript

interface Animal {
    makeSound(): void;
}

class Dog implements Animal {
    makeSound(): void {
        console.log("Woof");
    }
}

const dog: Animal = new Dog();
dog.makeSound(); // Output: Woof

Ví Dụ PHP

interface Animal {
    public function makeSound(): void;
}

class Dog implements Animal {
    public function makeSound(): void {
        echo "Woof";
    }
}

$dog = new Dog();
$dog->makeSound(); // Output: Woof

Python (Protocol trong Python 3.8+)

Mặc dù Python không có interface như Java hay TypeScript, nó sử dụng protocols từ module typing để đạt được hành vi tương tự.

from typing import Protocol

class Animal(Protocol):
    def make_sound(self) -> None:
        pass

class Dog:
    def make_sound(self) -> None:
        print("Woof")

def play_sound(animal: Animal) -> None:
    animal.make_sound()

play_sound(Dog())  # Output: Woof

Các Best Practices Khi Sử Dụng Interface

  1. Đặt Tên Rõ Ràng:
    • Quy ước chung: Sử dụng hậu tố "able" cho interface mô tả khả năng (ví dụ: Comparable, Cloneable, Payable, Serializable)
    • Đặt tên interface dựa trên chức năng của chúng, không phải bản chất của chúng
  2. Giữ Đơn Giản:
    • Chỉ định nghĩa các phương thức cần thiết để tránh interface phình to.
  3. Sử Dụng Phân Tách:
    • Tuân theo Nguyên Tắc Phân Tách Interface (ISP) bằng cách tạo các interface nhỏ, tập trung thay vì các Interface lớn, đa năng
  4. Thiết kế để mở rộng
    • Làm cho interface của bạn có khả năng thích ứng trong tương lai bằng cách cân nhắc các phần mở rộng tiềm năng
    • Tránh phá vỡ các thay đổi bằng cách lập kế hoạch cẩn thận cho thiết kế ban đầu
  5. Tài Liệu Tốt:
    • Cung cấp tài liệu rõ ràng cho interface và các phương thức của nó.

Các điểm cần tránh khi Sử Dụng Interface

  • Fat Interfaces: Tránh tạo giao diện với quá nhiều phương thức. Nếu một giao diện phát triển quá lớn, có thể cần phải chia thành các giao diện nhỏ hơn, tập trung hơn.

Các Trường Hợp Sử Dụng Phổ Biến

  • Phát Triển API: Định nghĩa các hợp đồng giữa các service
  • Dependency Injection: Interface thường được sử dụng trong các Frameword Dependency Injection để tách rời các thành phần, Tránh bị phụ thuộc cứng giữa các thành phần.
  • Plugin Architectures: Interface cho phép bạn xác định contract cho các plugin và thư viện(xác định cách khách hàng nên tương tác với thư viện.), cho phép các nhà phát triển bên thứ ba mở rộng ứng dụng của bạn.
  • Mocking trong Unit Tests: Interface giúp dễ dàng tạo các đối tượng giả lập cho mục đích thử nghiệm.

Ví dụ thực tế:

Xây dựng hệ thống thông báo

interface NotificationSender {
    void send(String recipient, String message);
    boolean isAvailable();
}

class EmailNotification implements NotificationSender {
    public void send(String recipient, String message) {
        // Email sending logic
    }
    
    public boolean isAvailable() {
        return true; // Check email service availability
    }
}

class SMSNotification implements NotificationSender {
    public void send(String recipient, String message) {
        // SMS sending logic
    }
    
    public boolean isAvailable() {
        return true; // Check SMS service availability
    }
}

class NotificationService {
    private List<NotificationSender> senders;
    
    public void notify(String recipient, String message) {
        for (NotificationSender sender : senders) {
            if (sender.isAvailable()) {
                sender.send(recipient, message);
                return;
            }
        }
        throw new NotificationException("No available notification sender");
    }
}

Kết Luận

Interface là nền tảng trong OOP để thiết kế các hệ thống mạnh mẽ, có khả năng mở rộng và dễ bảo trì. Chúng giúp thực thi các hợp đồng giữa các lớp, thúc đẩy khả năng tái sử dụng mã, và làm cho các ứng dụng dễ thích nghi hơn với sự thay đổi. Bằng cách tận dụng đúng cách các interface, bạn có thể xây dựng phần mềm dễ kiểm thử, mở rộng và bảo trì.

Việc thành thạo các interface sẽ nâng cao chất lượng mã của bạn(tổ chức tốt hơn, dễ sửa đổi và mở rộng theo thời gian) và cải thiện kỹ năng thiết kế phần mềm của bạn.

Đọc Thêm

  • Design Patterns: Elements of Reusable Object-Oriented Software của Erich Gamma và cộng sự.
  • Clean Code - Robert C. Martin
  • Effective Java - Joshua Bloch
  • Các Nguyên Tắc SOLID cho Thiết Kế Phần Mềm Tốt Hơn.
  • OOP theo từng ngôn ngữ(Java, PHP, TypeScript, Python Protocols,..)