Tính đa hình (Polymorphism)

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
}
?>
POLYMORPHISM - TÍNH ĐA HÌNH INTERFACE makeSound() DOG makeSound() { echo "Gâu gâu!"; } 🐕 Gâu gâu! CAT makeSound() { echo "Meo meo!"; } 🐈 Meo meo! BIRD makeSound() { echo "Chip chip!"; } 🐦 Chip chip! 💡 Cùng method makeSound(), mỗi class có hành vi riêng
Hình 1: Khái niệm Polymorphism - Một method, nhiều hình thái

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à public hoặc protected
  • ✅ Dùng khi muốn định nghĩa “khung” chung cho các class con
ABSTRACT CLASS vs INTERFACE ABSTRACT CLASS Từ khóa: abstract class Animal Có thể chứa: ✅ Properties ✅ Methods thường (có body) ✅ Abstract methods ✅ Constructor Kế thừa: extends (1 class) Dùng khi: • Quan hệ IS-A • Dog IS-A Animal • Có code chung INTERFACE Từ khóa: interface Swimmable Có thể chứa: ❌ Properties ❌ Methods thường ✅ Method signatures ❌ Constructor Kế thừa: implements (nhiều) Dùng khi: • Quan hệ CAN-DO • Dog CAN swim • Định nghĩa khả năng 💡 Abstract = Bản chất chung | Interface = Khả năng chung
Hình 2: Abstract Class vs Interface

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ểmAbstract ClassInterface
Từ khóaabstract classinterface
Kế thừaextends (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 modifierspublic/protected/privateChỉ public
Constructor✅ Có❌ Không
Khi nào dùngQuan 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(); ?>
POLYMORPHISM VỚI TYPE HINTING FUNCTION VỚI TYPE HINTING function processPayment(Payment $payment) { $payment->process(); ↓ Nhận bất kỳ object nào implement Payment ↓ CreditCard process() { Xử lý thẻ tín dụng } 💳 Thanh toán thẻ Momo process() { Xử lý ví Momo } 📱 Thanh toán Momo BankTransfer process() { Xử lý chuyển khoản ngân hàng } 🏦 Chuyển khoản 💡 Một function xử lý nhiều loại payment khác nhau
Hình 3: Ví dụ Polymorphism với Type Hinting

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\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:

  1. Encapsulation: Ẩn dữ liệu, cung cấp interface
  2. Inheritance: Kế thừa code từ class cha
  3. Polymorphism: Một interface, nhiều hình thái
  4. 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

  1. Bài 1: Tạo interface Sortable với method sort(). Tạo class BubbleSort, QuickSort, MergeSort implement interface. Function sortArray(Sortable $sorter, $array).
  2. Bài 2: Tạo abstract class Report với abstract method generate(). Tạo PDFReport, ExcelReport, HTMLReport extends Report. Method export(Report $report, $data).
  3. Bài 3: Tạo interface Cacheable (get, set, delete). Tạo FileCache, RedisCache, MemcachedCache implement. 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); ?>

Để lại bình luận

Email của bạn sẽ không được hiển thị.