Hướng dẫn polymorphism php example - ví dụ đa hình php

Vietnamese (Tiếng Việt) translation by Brian Vu (you can also view the original English article) Brian Vu (you can also view the original English article)

Nội dung chính ShowShow

  • Đa hình là gì?
  • Các Interface
  • Class Trừu Tượng (Abstract Class)
  • Bước 1: Xác Định Vấn Đề
  • Bước 2: Định Nghĩa Interface Của Bạn
  • Bước 3: Xây Dựng Class Thực Thi
  • Bước 4: Sử Dụng Đối Tượng Thực Thi
  • Lấy Một Writer
  • Bước 5: Tập Hợp Mọi Thứ Lại Cùng Nhau
  • Kết Luận

Nội dung chính

  • Đa hình là gì?
  • Các Interface
  • Class Trừu Tượng (Abstract Class)
  • Bước 1: Xác Định Vấn Đề
  • Bước 2: Định Nghĩa Interface Của Bạn
  • Bước 3: Xây Dựng Class Thực Thi
  • Bước 4: Sử Dụng Đối Tượng Thực Thi
  • Lấy Một Writer
  • Bước 5: Tập Hợp Mọi Thứ Lại Cùng Nhau
  • Kết Luận

Nội dung chính


Đa hình là gì?

Trong lập trình hướng đối tượng, đa hình là một công cụ mạnh mẽ và quan trọng. Nó có thể được dùng để tạo ra luồng có hệ thống hơn cho ứng dụng của bạn. Bài viết này sẽ mô tả khái quát khái niệm của tính đa hình, và bạn sẽ thấy nó được triển khai trong PHP dễ dàng như thế nào.

Đa hình (polymorphism) là 1 từ dài dành cho một khái niệm rất đơn giản.

Đa hình mô tả một mô hình của lập trình hướng đối tượng, mà ở đây các class có chức năng khác nhau nhưng chia sẻ cùng 1 interface.

Cái hay của tính đa hình là mã lệnh làm việc với nhiều class khác nhau không cần biết class nào đang được sử dụng, bởi chúng đều được sử dụng theo cùng 1 cách giống nhau.

Một ví dụ tương đồng cho tính đa hình trong thực tế, bạn có thể hình dung đó là "một cái nút". Mọi người đều biết cách sử dụng nó: đơn giản bạn chỉ cần nhấn nó. Vấn đề cái nút sẽ "thực thi" điều gì, tuy nhiên, còn phụ thuộc vào cái nó được kết nối đến và ngữ cảnh nó được sử dụng -- nhưng kết quả không ảnh hưởng tới cách sử dụng của nó. Nếu ông chủ kêu bạn nhấn một cái nút, bạn đã có đầy đủ thông tin cần thiết để thực hiện nhiệm vụ.


Các Interface

Class Trừu Tượng (Abstract Class)

Bước 1: Xác Định Vấn Đề

Bước 2: Định Nghĩa Interface Của Bạnphải thực thi toàn bộ các phương thức được định nghĩa trong interface. Bên cạnh đó, thì một class có thể thực thi nhiều interface.

Bước 3: Xây Dựng Class Thực Thi

interface MyInterface {
    // methods
}

Bước 4: Sử Dụng Đối Tượng Thực Thi

class MyClass implements MyInterface {
    // methods
}

Lấy Một Writer

interface MyInterface {
    public function doThis();
    public function doThat();
    public function setName($name);
}

Bước 5: Tập Hợp Mọi Thứ Lại Cùng Nhau

// VALID
class MyClass implements MyInterface {
    protected $name;
    public function doThis() {
        // code that does this
    }
    public function doThat() {
        // code that does that
    }
    public function setName($name) {
        $this->name = $name;
    }
}

// INVALID
class MyClass implements MyInterface {
    // missing doThis()!

    private function doThat() {
        // this should be public!
    }
    public function setName() {
        // missing the name argument!
    }
}

Class Trừu Tượng (Abstract Class)

Bước 1: Xác Định Vấn Đềphải thực thi toàn bộ các phương thức abstract được định nghĩa trong abstract class.

Bước 2: Định Nghĩa Interface Của Bạn

abstract class MyAbstract {
    // methods
}

Bước 3: Xây Dựng Class Thực Thi

class MyClass extends MyAbstract {
    // class methods
}

Bước 4: Sử Dụng Đối Tượng Thực Thi

Lấy Một Writer

Bước 1: Xác Định Vấn Đề

Bước 2: Định Nghĩa Interface Của Bạn

class poly_base_Article {
    public $title;
    public $author;
    public $date;
    public $category;

    public function  __construct($title, $author, $date, $category = 0) {
        $this->title = $title;
        $this->author = $author;
        $this->date = $date;
        $this->category = $category;
    }
}

Bước 3: Xây Dựng Class Thực Thi Các class trong ví dụ của bài viết này sử dụng quy ước đặt tên theo "package_component_Class" Đây là một cách phổ biến để tách rời các class thành namespace ảo nhằm tránh trùng lặp tên.

Bây giờ bạn muốn thêm một phương thức để xuất ra thông tin với nhiều đinh dạng khác nhau như XML hay JSON. Bạn có thể bị cám dỗ và thực hiện như sau:

class poly_base_Article {
    //...
    public function write($type) {
        $ret = '';
        switch($type) {
            case 'XML':
                $ret = '
'; $ret .= '' . $obj->title . ''; $ret .= '' . $obj->author . ''; $ret .= '' . $obj->date . ''; $ret .= '' . $obj->category . ''; $ret .= '
'; break; case 'JSON': $array = array('article' => $obj); $ret = json_encode($array); break; } return $ret; } }

Đây là một kiểu mã xấu, nhưng nó vẫn chạy -- ở thời điểm hiện tại. Hãy tự hỏi bản thân xem điều gì sẽ xảy đến trong tương lai, khi chúng ta muốn thêm nhiều định dạng hơn? Bạn có thể tiếp tục chỉnh sửa class, thêm ngày càng nhiều các trường hợp, nhưng bạn chỉ đang làm mất đi bản chất của class.

Một nguyên tắc quan trọng trong OOP là một class chỉ nên thực hiện một việc, và nó nên làm điều đó thật tốt.

Với khái niệm này, các câu lệnh điều kiện nên được coi như là một cờ báo hiệu rằng class của bạn đang làm quá nhiều thứ khác nhau. Đây chính là nơi đa hình được cần đến.

Trong ví dụ của chúng ta, rõ ràng có hai tác vụ được thực hiện: quản lý các article và định dạng dữ liệu của chúng. Trong bài viết này, chúng ta sẽ di dời phần mã lệnh định dạng dữ liệu vào một nhóm class mới và khám phá xem đa hình được sử dụng dễ dàng như thế nào.


Bước 2: Định Nghĩa Interface Của Bạn

Điều đầu tiên chúng ta nên làm là định nghĩa interface. Suy nghĩ cẩn thận về interface là điều rất quan trọng bạn nên làm, bởi bất kỳ sự thay đổi nào của nó có thể sẽ yêu cầu việc thay đổi các mã lệnh thực thi nó. Trong ví dụ của này, chúng ta sẽ sử dụng một interface đơn giản để định nghĩa một phương thức:

interface poly_writer_Writer {
    public function write(poly_base_Article $obj);
}

Chúng ta định nghĩa một phương thức public 

interface MyInterface {
    public function doThis();
    public function doThat();
    public function setName($name);
}
0, nhận tham số truyền vào là một đối tượng Article. Bất kỳ class nào thực thi interface Writer cũng sẽ phải có phương thức này trong nó.

Mẹo: Nếu bạn muốn giới hạn kiểu tham số truyền vào hàm hay phương thức, bạn có thể sử dụng type hints, tương tự như cách chúng ta đã làm với

interface MyInterface {
    public function doThis();
    public function doThat();
    public function setName($name);
}
0; nó chỉ cho phép đối tượng
interface MyInterface {
    public function doThis();
    public function doThat();
    public function setName($name);
}
2 truyền vào hàm. Không may, type hints chưa thể ứng dụng cho kiểu dữ liệu trả về, bởi PHP chưa hỗ trợ điều này, do đó bạn hãy tự kiểm soát vấn đề kiểu dữ liệu đầu ra nhé.
: Nếu bạn muốn giới hạn kiểu tham số truyền vào hàm hay phương thức, bạn có thể sử dụng type hints, tương tự như cách chúng ta đã làm với
interface MyInterface {
    public function doThis();
    public function doThat();
    public function setName($name);
}
0; nó chỉ cho phép đối tượng
interface MyInterface {
    public function doThis();
    public function doThat();
    public function setName($name);
}
2 truyền vào hàm. Không may, type hints chưa thể ứng dụng cho kiểu dữ liệu trả về, bởi PHP chưa hỗ trợ điều này, do đó bạn hãy tự kiểm soát vấn đề kiểu dữ liệu đầu ra nhé.


Bước 3: Xây Dựng Class Thực Thi

Với interface đã định nghĩa ở trên, giờ là lúc để xây dựng class thực thi nó. Trong ví dụ, chúng ta có hai kiểu định dạng muốn xuất ra. Do đó chúng ta sẽ có 2 class Writer: XMLWriter và JSONWriter. Dựa vào 2 class này, chúng ta sẽ lấy ra dữ liệu từ đối tượng Article và định dạng nó.

Class XMLWriter trông sẽ như sau:

class MyClass implements MyInterface {
    // methods
}
0

Bạn có thể thấy, chúng sử dụng từ khoá

class MyClass implements MyInterface {
    // methods
}
6 để thực thi interface của chúng ta. Phương thức
interface MyInterface {
    public function doThis();
    public function doThat();
    public function setName($name);
}
0 chứa mã xử lý cụ thể việc định dạng dữ liệu thành kiiểu XML.

Nào, bây giờ sẽ tới class JSONWriter:

class MyClass implements MyInterface {
    // methods
}
1

Các mã lệnh xử lý cho từng định dạng, giờ đây đã được chứa trong từng class riêng biệt. Mỗi class sẽ chịu trách nhiệm xử lý duy nhất một định dạng cụ thể, và chỉ có vậy. Không thành phần nào khác trong ứng dụng của bạn cần quan tâm về cách hoạt động của chúng mà vẫn có thể sử dụng chúng, cám ơn interface của chúng ta.


Bước 4: Sử Dụng Đối Tượng Thực Thi

Với những class mới đã được định nghĩa, giờ là thời điểm để chúng ta trở lại class Article. Toàn bộ mã lệnh thực thi ban đầu của phương thức

interface MyInterface {
    public function doThis();
    public function doThat();
    public function setName($name);
}
0 đã được viết lại trong các class mới. Mọi việc cần làm cho phương thức này của Article bây giờ, là sử dụng những class mới đã được tạo ra, như sau:

class MyClass implements MyInterface {
    // methods
}
2

Toàn bộ quy trình thực thi của phương thức này là ép một tham số truyền vào phải là đối tượng của class Writer (những đối tượng được khởi tạo từ bất kỳ class nào thực thi interface Writer), gọi phương thức 

interface MyInterface {
    public function doThis();
    public function doThat();
    public function setName($name);
}
0, truyền vào nó đối tượng nội tại class (
interface MyInterface {
    public function doThis();
    public function doThat();
    public function setName($name);
}
7), sau đó chuyển tiếp giá trị được trả về thẳng tới người dùng. Nó không còn cần phải lo lắng về chi tiết của việc thực thi định dạng dữ liệu, giờ đây, nó có thể tập trung vào nhiệm vụ chính của mình.

Lấy Một Writer

Bạn có thể tự hỏi đâu là nơi mà bạn lấy một đối tượng Writer để bắt đầu, vì bạn cần phải truyền một trong số chúng tới phương thức này. Điều này tùy thuộc vào bạn, có rất nhiều chiến lược để thực hiện nó. Ví dụ, bạn có thể sử dụng một factory class để nhận dữ liệu request và khởi tạo một đối tượng:

class MyClass implements MyInterface {
    // methods
}
3

Như tôi đã nói, có nhiều chiến lược khác nhau để sử dụng, tuỳ vào nhu cầu của bạn. Trong ví dụ này, một biến request chọn kiểu định dạng dữ liệu để sử dụng. Nó xây dựng một tên class từ biến request, kiểm tra sự tồn tại của class dựa trên tên của nó, trường hợp nó tồn tại thì trả về một đối tượng Writer mới. Nếu không có class nào tồn tại dưới cái tên được tạo ra, một exception sẽ được ném về cho người dùng để thông báo với họ điều gì đang xảy ra.


Bước 5: Tập Hợp Mọi Thứ Lại Cùng Nhau

Bước cuối cùng, sau khi đã có đủ các thành phần, chúng ta sẽ tập hợp chúng lại cùng nhau:

class MyClass implements MyInterface {
    // methods
}
4

Đầu tiên, chúng ta tạo ra một đối tượng Article để làm việc. Sau đó, chúng ta cố gắng lấy một đối tượng Writer từ Factory Class, trong trường hợp thất bại và một exception được ném ra, chúng ta sẽ khởi tạo lại một đối tương tượng mặc định (XMLWriter). Cuối cùng, chúng ta truyền đối tượng Writer tới phương thức write() của đối tượng Article, và xuất ra kết quả.


Kết Luận

Trong bài hướng dẫn này, tôi đã giới thiệu tới bạn về tính đa hình và giải thích về các interface trong PHP. Hy vọng bạn sẽ nhận ra rằng tôi mới chỉ thể hiện cho bạn thấy sức mạnh của đa hình ở một trường hợp. Còn rất, rất nhiều cách để ứng dụng nó. Đa hình là một phương pháp chuẩn mực để cách ly mã xấu ra khỏi mã lập trình hướng đối tượng của bạn. Nó tuân thủ nguyên tắc giữ các thành phần tách bạch, và là một phần không thể thiếu của nhiều mô hình thiết kế (design pattern). Nếu bạn có bất kỳ câu hỏi nào, đừng ngại để lại comment nhé!