Giao diện và lớp trừu tượng
Thuộc phần Lập trình hướng đối tượng trong hành trình PHP của Coddy. Bài học 30 trên 91.
Giờ bạn đã học về cả interface lẫn lớp trừu tượng, hãy cùng tìm hiểu khi nào nên dùng từng loại. Dù trông có vẻ giống nhau, chúng có mục đích và khả năng riêng biệt.
| Tính năng | Giao diện | Lớp trừu tượng |
|---|---|---|
| Phương thức | Chỉ có chữ ký phương thức | Có cả phương thức trừu tượng và phương thức cụ thể |
| Thuộc tính | Chỉ có hằng số | Có thể có thuộc tính |
| Kế thừa đa lớp | Một lớp có thể triển khai nhiều giao diện | Một lớp chỉ có thể kế thừa một lớp |
| Hàm khởi tạo | Có thể khai báo chữ ký __construct (không có phần thân) | Có thể định nghĩa hàm khởi tạo có phần thân |
Dùng interface khi bạn muốn định nghĩa một hợp đồng mà các lớp không liên quan có thể đáp ứng. Ví dụ, cả Printer và Logger đều có thể triển khai một interface Writable, dù chúng không có tổ tiên chung.
Dùng một lớp trừu tượng khi bạn có các lớp liên quan dùng chung đoạn code. Nếu cả lớp Dog và Cat đều cần cùng một cách triển khai phương thức eat(), lớp trừu tượng Animal cho phép bạn viết đoạn code đó một lần.
Bạn cũng có thể kết hợp cả hai cách. Một lớp có thể kế thừa một lớp trừu tượng đồng thời triển khai nhiều interface:
<?php
abstract class Animal {
protected $name;
public function eat() {
return $this->name . " is eating";
}
}
interface Swimmable {
public function swim();
}
class Duck extends Animal implements Swimmable {
public function __construct($name) {
$this->name = $name;
}
public function swim() {
return $this->name . " is swimming";
}
}
Điểm chính: Hãy chọn interface để xác định các khả năng dùng chung giữa những lớp không liên quan. Hãy chọn lớp trừu tượng khi bạn cần chia sẻ code giữa những lớp có liên quan.
Thử thách
DễHãy xây dựng một hệ thống phương tiện để minh họa khi nào nên dùng lớp trừu tượng thay vì giao diện, cũng như cách kết hợp hiệu quả cả hai cách tiếp cận.
Bạn sẽ tạo một hệ thống trong đó các phương tiện dùng chung thuộc tính thông qua kế thừa, đồng thời có thể có thêm năng lực nhờ giao diện. Điều này phản ánh các quyết định thiết kế trong thực tế: ô tô “là một” phương tiện (kế thừa), nhưng cũng “có khả năng” được tiếp nhiên liệu (giao diện).
Sắp xếp code của bạn trong bốn tệp:
Refuelable.php: Định nghĩa giao diệnRefuelablevới một khai báo phương thức duy nhất:refuel($amount). Năng lực này có thể áp dụng cho bất kỳ lớp nào cần được tiếp nhiên liệu, bất kể hệ thống phân cấp kế thừa của lớp đó.Vehicle.php: Định nghĩa lớp trừu tượngVehicle, cung cấp chức năng dùng chung cho mọi phương tiện. Lớp này cần có thuộc tính$brandđược bảo vệ và thuộc tính$fuelLevelđược bảo vệ (bắt đầu từ 0). Hàm khởi tạo nhận tên hãng. Thêm phương thức cụ thểgetBrand()trả về hãng, phương thức cụ thểgetFuelLevel()trả về mức nhiên liệu hiện tại và phương thức trừu tượngdrive()mà các lớp con phải triển khai.Car.php: Tạo lớpCarkế thừaVehiclevà triển khaiRefuelable. Thêm cả hai tệp bắt buộc ở đầu tệp. Hàm khởi tạo nhận hãng và truyền giá trị đó cho hàm khởi tạo của lớp cha. Triển khaidrive()để trả về"[brand] car is driving". Triển khairefuel($amount)để cộng lượng nhiên liệu vào mức nhiên liệu và trả về"[brand] refueled to [fuelLevel] liters".main.php: Thêm tệp Car. Bạn sẽ nhận hai đầu vào: tên hãng và lượng nhiên liệu (chuyển thành số nguyên). Tạo mộtCarvới tên hãng đó. In kết quả củadrive()trên dòng đầu tiên, sau đó in kết quả củarefuel()với lượng nhiên liệu trên dòng thứ hai.
Thử thách này làm rõ điểm khác biệt chính: lớp trừu tượng Vehicle chia sẻ code và thiết lập mối quan hệ “là một”, trong khi giao diện Refuelable bổ sung một năng lực có thể áp dụng cho những lớp không liên quan khác (như máy phát điện hoặc thuyền) mà không cần chúng phải là phương tiện.
Tự làm thử
<?php
require_once 'Car.php';
// Đọc đầu vào
$brand = trim(fgets(STDIN));
$fuelAmount = intval(trim(fgets(STDIN)));
// TODO: Tạo một Car với thương hiệu đã nhập
// TODO: In kết quả của drive() trên dòng đầu tiên
// TODO: In kết quả của refuel() với lượng nhiên liệu đã nhập trên dòng thứ hai
?>Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng về OOP
Tệp bên ngoàiGiới thiệu về OOPLớp và đối tượngTừ khóa $thisPhương thứcThuộc tínhHàm khởi tạo (__construct)Hàm hủy (__destruct)Ôn tập: máy tính đơn giản4Kế thừa
Kế thừa cơ bảnTừ khóa parent::Ghi đè phương thứcTừ khóa finalLớp trừu tượngÔn tập: hệ thống phân cấp nhân viên7Đóng gói
Công khai, được bảo vệ, riêng tưTìm hiểu sâu về bộ điều chỉnh quyền truy cậpGetter và setterẨn thông tinRút gọn hàm khởi tạo (8.0)Ôn tập: hệ thống hồ sơ học sinh2Không gian tên và tự động nạp
Giới thiệu về không gian tênTừ khóa useChuẩn tự động nạp PSR-4Trình tự động nạp của ComposerÔn tập: dự án có tổ chức5Giao diện và hợp đồng
Giới thiệu về giao diệnTriển khai giao diệnTriển khai nhiều giao diệnGiao diện và lớp trừu tượngGợi ý kiểu với giao diệnÔn tập: Máy tính hình học8Phương thức ma thuật
Giới thiệu về phương thức ma thuật__toString và __debugInfo__get, __set, __isset, __unset__call và __callStatic__clone và sao chép đối tượng__serialize và __unserializeÔn tập: Bộ sưu tập tùy chỉnh3Thuộc tính của lớp
Thuộc tính instance và staticHằng số trong lớpPhương thức và thuộc tính staticThuộc tính private và protectedThuộc tính chỉ đọc (PHP 8.1)Ôn tập: Trình quản lý tài khoản ngân hàng6Đa hình
Xem lại việc ghi đè phương thứcĐa hình qua giao diệnGợi ý kiểu và kiểu hợpLiên kết tĩnh muộnÔn tập: bộ xử lý thanh toánTự luyện tập: Trình biên dịch PHP online