Sau khi nắm vững Tính đóng gói (Encapsulation), bước cuối cùng trong 4 trụ cột OOP là Tính đa hình (Polymorphism). Polymorphism cho phép một interface có nhiều hình thái khác nhau – cùng một method name nhưng hành vi khác nhau tùy theo class. Đây là nền tảng để viết code linh hoạt, dễ mở rộng và bảo trì.
1. Polymorphism là gì?
Polymorphism (Tính đa hình) có nghĩa là “nhiều hình thái”. Trong OOP, nó cho phép các object khác nhau phản hồi cùng một method call theo cách riêng của chúng.
1.1. Ví dụ thực tế
Giống như phím “Play” trên remote:
- 📺 TV: Play → Bật kênh
- 🎵 Radio: Play → Phát nhạc
- 🎮 Game: Play → Bắt đầu chơi
Cùng một nút “Play” nhưng hành vi khác nhau tùy thiết bị.
1.2. Vấn đề khi không có Polymorphism
<?php
// ❌ Không có Polymorphism - code rối, khó mở rộng
class Dog {
public function makeSound() {
echo "Gâu gâu!";
}
}
class Cat {
public function makeNoise() { // Tên khác!
echo "Meo meo!";
}
}
class Bird {
public function sing() { // Tên khác!
echo "Chip chip!";
}
}
// Phải xử lý từng loại riêng
$animals = [new Dog(), new Cat(), new Bird()];
foreach ($animals as $animal) {
if ($animal instanceof Dog) {
$animal->makeSound();
} elseif ($animal instanceof Cat) {
$animal->makeNoise();
} elseif ($animal instanceof Bird) {
$animal->sing();
}
}
// Code dài, khó bảo trì, thêm loại mới phải sửa nhiều chỗ
?>1.3. Giải pháp: Dùng Polymorphism
<?php
// ✅ Có Polymorphism - code ngắn gọn, dễ mở rộng
abstract class Animal {
abstract public function makeSound(); // Bắt buộc override
}
class Dog extends Animal {
public function makeSound() {
echo "Gâu gâu!";
}
}
class Cat extends Animal {
public function makeSound() {
echo "Meo meo!";
}
}
class Bird extends Animal {
public function makeSound() {
echo "Chip chip!";
}
}
// Code đơn giản, dễ mở rộng
$animals = [new Dog(), new Cat(), new Bird()];
foreach ($animals as $animal) {
$animal->makeSound(); // Cùng tên method, hành vi khác nhau
}
?>2. Các loại Polymorphism
2.1. Method Overriding (Compile-time Polymorphism)
Class con ghi đè method của class cha với implementation riêng.
<?php
class Shape {
public function draw() {
echo "Vẽ hình";
}
public function getArea() {
return 0;
}
}
class Circle extends Shape {
private $radius;
public function __construct($radius) {
$this->radius = $radius;
}
// Override draw()
public function draw() {
echo "Vẽ hình tròn bán kính {$this->radius}";
}
// Override getArea()
public function getArea() {
return pi() * $this->radius * $this->radius;
}
}
class Rectangle extends Shape {
private $width;
private $height;
public function __construct($width, $height) {
$this->width = $width;
$this->height = $height;
}
// Override draw()
public function draw() {
echo "Vẽ hình chữ nhật {$this->width}x{$this->height}";
}
// Override getArea()
public function getArea() {
return $this->width * $this->height;
}
}
// Sử dụng
$shapes = [
new Circle(5),
new Rectangle(10, 20),
new Circle(7)
];
foreach ($shapes as $shape) {
$shape->draw();
echo " - Diện tích: " . $shape->getArea() . "
";
}
// Cùng method draw() và getArea(), nhưng hành vi khác nhau
?>3. Abstract Class (Lớp trừu tượng)
Abstract Class là class không thể tạo object trực tiếp, chỉ dùng làm class cha để các class con kế thừa. Chứa abstract methods – methods không có body, bắt buộc class con phải override.
3.1. Cú pháp Abstract Class
<?php
abstract class Payment {
protected $amount;
protected $status = "pending";
public function __construct($amount) {
$this->amount = $amount;
}
// Method thường - có body
public function getAmount() {
return $this->amount;
}
public function getStatus() {
return $this->status;
}
// Abstract method - không có body, bắt buộc override
abstract public function process();
abstract public function refund();
}
class CreditCardPayment extends Payment {
private $cardNumber;
public function __construct($amount, $cardNumber) {
parent::__construct($amount);
$this->cardNumber = $cardNumber;
}
// Bắt buộc phải implement
public function process() {
echo "Xử lý thanh toán thẻ tín dụng: " .
number_format($this->amount) . " VNĐ
";
$this->status = "completed";
return true;
}
public function refund() {
echo "Hoàn tiền về thẻ: " . number_format($this->amount) . " VNĐ
";
$this->status = "refunded";
return true;
}
}
class MomoPayment extends Payment {
private $phoneNumber;
public function __construct($amount, $phoneNumber) {
parent::__construct($amount);
$this->phoneNumber = $phoneNumber;
}
public function process() {
echo "Xử lý thanh toán Momo: " .
number_format($this->amount) . " VNĐ
";
$this->status = "completed";
return true;
}
public function refund() {
echo "Hoàn tiền vào ví Momo: " . number_format($this->amount) . " VNĐ
";
$this->status = "refunded";
return true;
}
}
// Sử dụng
function processPayment(Payment $payment) {
echo "Đang xử lý thanh toán...
";
$payment->process();
echo "Trạng thái: " . $payment->getStatus() . "
";
}
$payments = [
new CreditCardPayment(1000000, "1234567890123456"),
new MomoPayment(500000, "0912345678")
];
foreach ($payments as $payment) {
processPayment($payment);
}
// ❌ Không thể tạo object từ abstract class
// $payment = new Payment(1000); // Lỗi!
?>3.2. Quy tắc Abstract Class
- ✅ Không thể tạo object từ abstract class (
new AbstractClass()→ lỗi) - ✅ Có thể chứa properties, methods thường và abstract methods
- ✅ Abstract method không có body (
{}) - ✅ Class con bắt buộc implement tất cả abstract methods
- ✅ Abstract method phải là
publichoặcprotected - ✅ Dùng khi muốn định nghĩa “khung” chung cho các class con
4. Interface (Giao diện)
Interface là “hợp đồng” định nghĩa các methods mà class phải implement. Chỉ chứa method signatures (không có body), không có properties.
4.1. Cú pháp Interface
<?php
interface Drawable {
public function draw();
public function erase();
}
interface Resizable {
public function resize($width, $height);
}
// Class implement interface
class Circle implements Drawable, Resizable {
private $radius;
private $x;
private $y;
public function __construct($radius, $x = 0, $y = 0) {
$this->radius = $radius;
$this->x = $x;
$this->y = $y;
}
// Bắt buộc implement tất cả methods từ interface
public function draw() {
echo "Vẽ hình tròn tại ({$this->x}, {$this->y}) bán kính {$this->radius}
";
}
public function erase() {
echo "Xóa hình tròn
";
}
public function resize($width, $height) {
$this->radius = ($width + $height) / 2;
echo "Thay đổi bán kính thành {$this->radius}
";
}
}
class Rectangle implements Drawable, Resizable {
private $width;
private $height;
public function __construct($width, $height) {
$this->width = $width;
$this->height = $height;
}
public function draw() {
echo "Vẽ hình chữ nhật {$this->width}x{$this->height}
";
}
public function erase() {
echo "Xóa hình chữ nhật
";
}
public function resize($width, $height) {
$this->width = $width;
$this->height = $height;
echo "Thay đổi kích thước thành {$this->width}x{$this->height}
";
}
}
// Sử dụng
function drawShape(Drawable $shape) {
$shape->draw();
}
function resizeShape(Resizable $shape, $w, $h) {
$shape->resize($w, $h);
}
$circle = new Circle(10, 5, 5);
$rectangle = new Rectangle(20, 30);
drawShape($circle); // Polymorphism
drawShape($rectangle); // Polymorphism
resizeShape($circle, 15, 15);
resizeShape($rectangle, 25, 35);
?>4.2. Quy tắc Interface
- ✅ Chỉ chứa method signatures (không có body)
- ✅ Không có properties (từ PHP 8.1 có constants)
- ✅ Tất cả methods phải là
public - ✅ Class có thể implement nhiều interfaces (
implements A, B, C) - ✅ Interface có thể extends interface khác
- ✅ Dùng khi muốn định nghĩa “khả năng” (ability) chung
5. Abstract Class vs Interface
| Đặc điểm | Abstract Class | Interface |
|---|---|---|
| Từ khóa | abstract class | interface |
| Kế thừa | extends (1 class) | implements (nhiều) |
| Properties | ✅ Có | ❌ Không (chỉ constants) |
| Methods thường | ✅ Có (có body) | ❌ Không (PHP 8+ có default) |
| Abstract methods | ✅ Có | ✅ Tất cả đều abstract |
| Access modifiers | public/protected/private | Chỉ public |
| Constructor | ✅ Có | ❌ Không |
| Khi nào dùng | Quan hệ “IS-A” | Quan hệ “CAN-DO” |
<?php
// Abstract Class - quan hệ IS-A (Dog IS-A Animal)
abstract class Animal {
protected $name; // Có properties
public function __construct($name) { // Có constructor
$this->name = $name;
}
public function getName() { // Method thường có body
return $this->name;
}
abstract public function makeSound(); // Abstract method
}
// Interface - quan hệ CAN-DO (Dog CAN swim, CAN run)
interface Swimmable {
public function swim(); // Chỉ signature
}
interface Runnable {
public function run();
}
// Class implement cả abstract class và interfaces
class Dog extends Animal implements Swimmable, Runnable {
public function makeSound() {
echo "{$this->name} says: Gâu gâu!
";
}
public function swim() {
echo "{$this->name} đang bơi
";
}
public function run() {
echo "{$this->name} đang chạy
";
}
}
$dog = new Dog("Buddy");
$dog->makeSound();
$dog->swim();
$dog->run();
?>6. Type Hinting với Polymorphism
Type Hinting cho phép chỉ định kiểu dữ liệu của tham số, kết hợp với Polymorphism tạo code linh hoạt.
<?php
interface Logger {
public function log($message);
}
class FileLogger implements Logger {
private $filename;
public function __construct($filename) {
$this->filename = $filename;
}
public function log($message) {
file_put_contents($this->filename, date("Y-m-d H:i:s") . " - $message\n", FILE_APPEND);
}
}
class DatabaseLogger implements Logger {
private $conn;
public function __construct($conn) {
$this->conn = $conn;
}
public function log($message) {
$stmt = $this->conn->prepare("INSERT INTO logs (message, created_at) VALUES (?, NOW())");
$stmt->execute([$message]);
}
}
class EmailLogger implements Logger {
private $email;
public function __construct($email) {
$this->email = $email;
}
public function log($message) {
mail($this->email, "Log Alert", $message);
}
}
// Function nhận bất kỳ Logger nào
function processOrder(Logger $logger) {
$logger->log("Đơn hàng được tạo");
// Logic xử lý đơn hàng...
$logger->log("Đơn hàng hoàn thành");
}
// Linh hoạt chọn logger
$fileLogger = new FileLogger("app.log");
$dbLogger = new DatabaseLogger($pdo);
$emailLogger = new EmailLogger("admin@site.com");
processOrder($fileLogger); // Log vào file
processOrder($dbLogger); // Log vào database
processOrder($emailLogger); // Log qua email
// Dễ dàng thêm logger mới mà không sửa code cũ
?>7. Ví dụ thực tế
7.1. Hệ thống Notification
<?php
interface Notifiable {
public function send($recipient, $message);
}
class EmailNotification implements Notifiable {
public function send($recipient, $message) {
echo "📧 Gửi email đến $recipient: $message
";
// mail($recipient, "Thông báo", $message);
}
}
class SMSNotification implements Notifiable {
public function send($recipient, $message) {
echo "📱 Gửi SMS đến $recipient: $message
";
// API gửi SMS
}
}
class PushNotification implements Notifiable {
public function send($recipient, $message) {
echo "🔔 Gửi push notification đến $recipient: $message
";
// Firebase Cloud Messaging
}
}
class SlackNotification implements Notifiable {
public function send($recipient, $message) {
echo "💬 Gửi Slack message đến $recipient: $message
";
// Slack API
}
}
class NotificationService {
private $notifiers = [];
public function addNotifier(Notifiable $notifier) {
$this->notifiers[] = $notifier;
}
public function notify($recipient, $message) {
foreach ($this->notifiers as $notifier) {
$notifier->send($recipient, $message);
}
}
}
// Sử dụng
$service = new NotificationService();
$service->addNotifier(new EmailNotification());
$service->addNotifier(new SMSNotification());
$service->addNotifier(new PushNotification());
$service->notify("user@email.com", "Đơn hàng của bạn đã được xác nhận");
// Dễ dàng thêm channel mới
$service->addNotifier(new SlackNotification());
?>7.2. Hệ thống Export
<?php
abstract class DataExporter {
protected $data;
public function __construct($data) {
$this->data = $data;
}
// Template method
public function export($filename) {
$content = $this->formatData();
$this->writeFile($filename, $content);
echo "✓ Exported to $filename
";
}
// Abstract method - bắt buộc override
abstract protected function formatData();
// Method chung
protected function writeFile($filename, $content) {
file_put_contents($filename, $content);
}
}
class CSVExporter extends DataExporter {
protected function formatData() {
$csv = "";
// Header
if (!empty($this->data)) {
$csv .= implode(",", array_keys($this->data[0])) . "\n";
}
// Rows
foreach ($this->data as $row) {
$csv .= implode(",", $row) . "\n";
}
return $csv;
}
}
class JSONExporter extends DataExporter {
protected function formatData() {
return json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
}
class XMLExporter extends DataExporter {
protected function formatData() {
$xml = "\n\n";
foreach ($this->data as $row) {
$xml .= " - \n";
foreach ($row as $key => $value) {
$xml .= " <$key>$value$key>\n";
}
$xml .= "
\n";
}
$xml .= " ";
return $xml;
}
}
// Sử dụng
$data = [
['id' => 1, 'name' => 'Product A', 'price' => 100000],
['id' => 2, 'name' => 'Product B', 'price' => 200000],
['id' => 3, 'name' => 'Product C', 'price' => 150000]
];
$exporters = [
new CSVExporter($data),
new JSONExporter($data),
new XMLExporter($data)
];
foreach ($exporters as $i => $exporter) {
$format = ['csv', 'json', 'xml'][$i];
$exporter->export("products.$format");
}
?>7.3. Hệ thống Database Adapter
<?php
interface DatabaseAdapter {
public function connect();
public function query($sql);
public function fetch();
public function disconnect();
}
class MySQLAdapter implements DatabaseAdapter {
private $conn;
private $result;
public function connect() {
$this->conn = new PDO("mysql:host=localhost;dbname=test", "root", "");
echo "✓ Connected to MySQL
";
}
public function query($sql) {
$this->result = $this->conn->query($sql);
}
public function fetch() {
return $this->result->fetchAll(PDO::FETCH_ASSOC);
}
public function disconnect() {
$this->conn = null;
echo "✓ Disconnected from MySQL
";
}
}
class PostgreSQLAdapter implements DatabaseAdapter {
private $conn;
private $result;
public function connect() {
$this->conn = new PDO("pgsql:host=localhost;dbname=test", "postgres", "password");
echo "✓ Connected to PostgreSQL
";
}
public function query($sql) {
$this->result = $this->conn->query($sql);
}
public function fetch() {
return $this->result->fetchAll(PDO::FETCH_ASSOC);
}
public function disconnect() {
$this->conn = null;
echo "✓ Disconnected from PostgreSQL
";
}
}
class Database {
private $adapter;
public function __construct(DatabaseAdapter $adapter) {
$this->adapter = $adapter;
$this->adapter->connect();
}
public function getUsers() {
$this->adapter->query("SELECT * FROM users");
return $this->adapter->fetch();
}
public function __destruct() {
$this->adapter->disconnect();
}
}
// Dễ dàng chuyển đổi database
$mysql = new Database(new MySQLAdapter());
$users = $mysql->getUsers();
// Chỉ cần thay adapter, code không đổi
$pgsql = new Database(new PostgreSQLAdapter());
$users = $pgsql->getUsers();
?>8. Lợi ích của Polymorphism
8.1. Code dễ mở rộng
<?php
// Thêm payment method mới không cần sửa code cũ
class PayPalPayment extends Payment {
public function process() {
// Logic PayPal
}
}
// Code cũ vẫn hoạt động
processPayment(new PayPalPayment(1000000));
?>8.2. Code dễ bảo trì
<?php
// Thay đổi implementation không ảnh hưởng interface
class ImprovedFileLogger implements Logger {
public function log($message) {
// Cải tiến: compress, encrypt, rotate logs...
}
}
// Code gọi không cần sửa
processOrder(new ImprovedFileLogger("app.log"));
?>8.3. Code dễ test
<?php
// Mock logger cho testing
class MockLogger implements Logger {
public $messages = [];
public function log($message) {
$this->messages[] = $message;
}
}
// Test function
$mockLogger = new MockLogger();
processOrder($mockLogger);
assert(count($mockLogger->messages) === 2);
assert($mockLogger->messages[0] === "Đơn hàng được tạo");
?>9. Best Practices
✅ NÊN:
- Dùng interface cho “khả năng” chung (Swimmable, Sortable, Cacheable)
- Dùng abstract class cho “bản chất” chung (Animal, Shape, Vehicle)
- Type hint với interface/abstract class thay vì concrete class
- Đặt tên interface:
-able,-ible(Drawable, Readable)- Một class implement nhiều interfaces
❌ KHÔNG NÊN:
- Lạm dụng abstract class/interface khi không cần
- Tạo interface chỉ có 1 method đơn giản
- Abstract class quá nhiều abstract methods
- Type hint với concrete class khi có thể dùng interface
Tóm tắt
Qua bài này bạn đã hoàn thành 4 trụ cột OOP:
- ✅ Polymorphism: Một interface, nhiều hình thái
- ✅ Method Overriding: Class con ghi đè method của class cha
- ✅ Abstract Class: Class không thể tạo object, chứa abstract methods
- ✅ Abstract Method: Method không có body, bắt buộc override
- ✅ Interface: “Hợp đồng” định nghĩa methods phải implement
- ✅ Type Hinting: Chỉ định kiểu dữ liệu tham số
- ✅ IS-A: Dùng abstract class (Dog IS-A Animal)
- ✅ CAN-DO: Dùng interface (Dog CAN swim)
4 Trụ cột OOP:
- Encapsulation: Ẩn dữ liệu, cung cấp interface
- Inheritance: Kế thừa code từ class cha
- Polymorphism: Một interface, nhiều hình thái
- Abstraction: Ẩn chi tiết, lộ bản chất (qua Abstract/Interface)
Bài tiếp theo, bạn sẽ học về Traits – tái sử dụng code ngang hàng.
🎯 Bài tập thực hành
- Bài 1: Tạo interface
Sortablevới method sort(). Tạo classBubbleSort,QuickSort,MergeSortimplement interface. Function sortArray(Sortable $sorter, $array). - Bài 2: Tạo abstract class
Reportvới abstract method generate(). TạoPDFReport,ExcelReport,HTMLReportextends Report. Method export(Report $report, $data). - Bài 3: Tạo interface
Cacheable(get, set, delete). TạoFileCache,RedisCache,MemcachedCacheimplement. Function cacheData(Cacheable $cache, $key, $value).
Gợi ý Bài 1:
<?php
interface Sortable {
public function sort(array $array): array;
}
class BubbleSort implements Sortable {
public function sort(array $array): array {
$n = count($array);
for ($i = 0; $i < $n; $i++) {
for ($j = 0; $j < $n - $i - 1; $j++) {
if ($array[$j] > $array[$j + 1]) {
$temp = $array[$j];
$array[$j] = $array[$j + 1];
$array[$j + 1] = $temp;
}
}
}
echo "Sorted with Bubble Sort
";
return $array;
}
}
class QuickSort implements Sortable {
public function sort(array $array): array {
if (count($array) <= 1) {
return $array;
}
$pivot = $array[0];
$left = $right = [];
for ($i = 1; $i < count($array); $i++) {
if ($array[$i] < $pivot) {
$left[] = $array[$i];
} else {
$right[] = $array[$i];
}
}
echo "Sorted with Quick Sort
";
return array_merge($this->sort($left), [$pivot], $this->sort($right));
}
}
function sortArray(Sortable $sorter, array $array): array {
echo "Input: " . implode(", ", $array) . "
";
$result = $sorter->sort($array);
echo "Output: " . implode(", ", $result) . "
";
return $result;
}
$data = [64, 34, 25, 12, 22, 11, 90];
sortArray(new BubbleSort(), $data);
sortArray(new QuickSort(), $data);
?>