Trong lập trình hướng đối tượng (OOP – Object-Oriented Programming) PHP, cả InterfaceAbstract class đều là những thành phần quan trọng giúp tạo ra cấu trúc và tổ chức mã nguồn một cách logic, dễ quản lý. Tuy nhiên, giữa chúng có sự khác biệt rõ ràng về cách sử dụng, khả năng kế thừa, và mục đích sử dụng trong các trường hợp khác nhau.

Trong bài viết này, chúng ta sẽ phân tích chi tiết về sự khác nhau giữa Interface và Abstract class trong PHP, kèm theo các ví dụ minh họa dễ hiểu để bạn có thể nắm vững kiến thức này.


1. Định nghĩa cơ bản

Interface

Interface là một hợp đồng (contract) giữa các lớp, cho phép chúng thực hiện một tập hợp các phương thức nhất định mà không cần phải cung cấp phần cài đặt cụ thể. Interface chỉ chứa các phương thức khai báo (method declaration), không có phần định nghĩa thực hiện.

Cú pháp của Interface:
interface Shape {
    public function getArea();
    public function getPerimeter();
}
  • Đặc điểm: Tất cả các phương thức trong Interface đều là public và không có phần định nghĩa. Không thể chứa thuộc tính (property).
  • Kế thừa: Một lớp có thể implement nhiều Interface cùng lúc.

Abstract Class

Abstract class là một lớp có thể chứa các phương thức đã được định nghĩa (method definition) và cả các phương thức trừu tượng (abstract method) chưa có phần định nghĩa. Lớp con phải thực hiện các phương thức trừu tượng này.

Cú pháp của Abstract class:
abstract class Shape {
    abstract public function getArea();
    
    public function describe() {
        return "This is a shape.";
    }
}
  • Đặc điểm: Có thể chứa cả thuộc tính và phương thức (đã và chưa định nghĩa). Các phương thức có thể có nhiều quyền truy cập khác nhau (public, protected, private).
  • Kế thừa: Một lớp chỉ có thể kế thừa một Abstract class (vì PHP không hỗ trợ đa kế thừa cho các lớp).

2. Sự khác nhau chi tiết giữa Interface và Abstract Class

a. Phương thức khai báo và định nghĩa

Interface: Chỉ cho phép khai báo phương thức mà không có phần cài đặt. Khi một lớp implement một Interface, nó bắt buộc phải cung cấp phần cài đặt cho tất cả các phương thức đã khai báo trong Interface.

Ví dụ Interface:

interface Animal {
    public function makeSound();
}

class Dog implements Animal {
    public function makeSound() {
        return "Woof!";
    }
}

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

Trong ví dụ trên, Dog là một lớp kế thừa (implement) từ Interface Animal và bắt buộc phải định nghĩa phương thức makeSound().

Abstract Class: Có thể chứa cả phương thức đã định nghĩa và phương thức trừu tượng. Các lớp con kế thừa Abstract class không bắt buộc phải định nghĩa lại các phương thức đã được định nghĩa sẵn, nhưng phải định nghĩa các phương thức trừu tượng.

Ví dụ Abstract Class:

abstract class Animal {
    abstract public function makeSound();
    
    public function move() {
        return "I can move.";
    }
}

class Cat extends Animal {
    public function makeSound() {
        return "Meow!";
    }
}

$cat = new Cat();
echo $cat->makeSound(); // Output: Meow!
echo $cat->move(); // Output: I can move.

Lớp Cat kế thừa từ Animal và phải định nghĩa phương thức trừu tượng makeSound(). Tuy nhiên, nó có thể sử dụng phương thức move() mà không cần định nghĩa lại.

b. Kế thừa và đa kế thừa

Interface: Một lớp trong PHP có thể implement nhiều Interface cùng một lúc, điều này mang lại khả năng đa kế thừa linh hoạt.Ví dụ:

interface CanFly {
    public function fly();
}

interface CanSwim {
    public function swim();
}

class Duck implements CanFly, CanSwim {
    public function fly() {
        return "I'm flying!";
    }

    public function swim() {
        return "I'm swimming!";
    }
}

$duck = new Duck();
echo $duck->fly(); // Output: I'm flying!
echo $duck->swim(); // Output: I'm swimming!

Lớp Duck thực hiện cả hai Interface CanFlyCanSwim, nên nó cần cung cấp phần cài đặt cho cả hai phương thức fly()swim().

Abstract Class: PHP không hỗ trợ đa kế thừa các lớp. Một lớp chỉ có thể kế thừa một Abstract class. Nếu muốn kế thừa nhiều lớp trừu tượng, bạn sẽ phải sử dụng các kỹ thuật khác như traits.

abstract class Bird {
    abstract public function fly();
}

class Sparrow extends Bird {
    public function fly() {
        return "I'm a sparrow, and I'm flying!";
    }
}

$sparrow = new Sparrow();
echo $sparrow->fly(); // Output: I'm a sparrow, and I'm flying!

c. Thuộc tính

Interface: Không thể chứa thuộc tính, chỉ có thể khai báo phương thức.

Abstract Class: Có thể chứa cả thuộc tính và phương thức đã định nghĩa sẵn.

abstract class Animal {
    public $name;

    public function setName($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }

    abstract public function makeSound();
}

class Dog extends Animal {
    public function makeSound() {
        return "Woof!";
    }
}

$dog = new Dog();
$dog->setName("Buddy");
echo $dog->getName(); // Output: Buddy
echo $dog->makeSound(); // Output: Woof!

Trong ví dụ này, lớp Animal có một thuộc tính $name và các phương thức setName()getName(). Lớp Dog kế thừa và sử dụng những phương thức này mà không cần định nghĩa lại.

d. Khi nào sử dụng Interface và Abstract Class?

  • Interface: Nên sử dụng khi bạn muốn đảm bảo rằng các lớp tuân thủ một bộ các phương thức nhất định mà không quan tâm đến cách chúng được cài đặt. Interface được dùng để tạo ra một hợp đồng giữa các lớp, đặc biệt hữu ích trong các mô hình đa kế thừa.
  • Abstract Class: Nên sử dụng khi bạn có các phương thức chung cho một nhóm đối tượng và muốn lớp con có thể kế thừa một số chức năng, nhưng vẫn bắt buộc phải cung cấp các phương thức riêng (abstract method) cho những phần cài đặt cụ thể.

3. Tổng kết

Sự khác biệt giữa InterfaceAbstract class trong PHP chủ yếu nằm ở mục đích sử dụng và các quy tắc về kế thừa, tính đa hình (polymorphism). Trong khi Interface cung cấp một cách để tạo ra các hợp đồng thực hiện, thì Abstract class cho phép tạo ra một cấu trúc nền tảng với các phương thức và thuộc tính đã có sẵn, đồng thời yêu cầu lớp con phải cài đặt các phương thức cụ thể.

4. Ví dụ so sánh thực tế

Giả sử bạn đang phát triển một hệ thống quản lý động vật trong một sở thú. Bạn có các loại động vật khác nhau, một số biết bay, một số biết bơi. Bạn sẽ sử dụng Interface để yêu cầu tất cả các loài động vật biết bay phải có phương thức fly(), và các loài biết bơi phải có phương thức swim(). Tuy nhiên, bạn vẫn có thể tạo một lớp trừu tượng Animal để quản lý các thuộc tính chung như tên, tuổi của động vật.

interface CanFly {
    public function fly();
}

interface CanSwim {
    public function swim();
}

abstract class Animal {
    public $name;

    public function setName($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }

    abstract public function makeSound();
}

class Duck extends Animal implements CanFly, CanSwim {
    public function fly() {
        return "I'm flying!";
    }

    public function swim() {
        return "I'm swimming!";
    }

    public function makeSound() {
        return "Quack!";
    }
}

$duck = new Duck();
$duck->setName("Donald");
echo $duck->getName(); // Output: Donald
echo $duck->fly(); // Output: I'm flying!
echo $duck->swim(); // Output: I'm swimming!
echo $duck->makeSound(); // Output: Quack!

Qua ví dụ này, chúng ta có thể thấy cách kết hợp giữa Interface và Abstract class để tận dụng sức mạnh của cả hai trong một ứng dụng thực tế.

Việc sử dụng InterfaceAbstract class trong PHP phụ thuộc vào tình huống cụ thể của dự án. Khi được sử dụng đúng cách, chúng giúp tổ chức mã nguồn tốt hơn, tăng tính tái sử dụng và mở rộng, đồng thời giảm thiểu lỗi lập trình.