Ra mắt hai series mới cực hot Trí tuệ nhân tạo A đến ZPython công cụ không thể thiếu khi nghiên cứu Data science, Machine learning.

Lập trình hướng đối tượng trong PHP - Phần 3 Đa dạng hóa trong kế thừa

Chúng ta đang ở phần thứ 3 trong loạt bài về Lập trình hướng đối tượng trong PHP:

1. Abstract class và abstract method

Trong nhiều tình huống khi thực hiện kế thừa, chúng ta không bao giờ tạo ra một đối tượng từ class cha. Trong ví dụ về class Pet ở phần trước, chúng ta thường tạo ra các đối tượng từ class con là Dog, Cat chứ không tạo các đối tượng từ class Pet. Lập trình hướng đối tượng có một khái niệm lớp trìu tượng (abstract class) dùng cho các trường hợp này, nó khác với các lớp tiêu chuẩn thông thường. Abstract class là một phiên bản tạm của lớp cha, với định nghĩa abstract class, bạn có thể biểu thị các hành vi chung của các lớp con cần có. Khi bạn cố tình tạo ra một đối tượng từ abstract class, một lỗi nghiêm trọng sẽ được sinh ra. Để tạo ra một class trìu tượng chúng ta sử dụng từ khóa abstract:

abstract class ClassName {
}

Các lớp trìu tượng thường có các phương thức trìu tượng, chúng được định nghĩa như sau:

abstract function methodName();
abstract function methodName ($var1, $var2);

Trong các phương thức trìu tượng, bạn không thực hiện các công việc cụ thể mà các công việc này sẽ được viết mã trong các lớp được mở rộng từ lớp trìu tượng.

abstract class Pet {
  protected $_name;
  abstract public function getName();
}

Khi đó class Cat mở rộng từ lớp trìu tượng Pet mới viết code cho phương thức getName():

class Cat extends Pet {
  function getName() {
    return $this->_name;
  }
}

Chú ý khi định nghĩa một phương thức kế thừa từ một lớp trìu tượng, ví dụ Cat::getName() phải có phạm vi truy xuất giống hoặc rộng hơn phương thức trìu tượng ở lớp cha. Ví dụ phương thức trìu tượng là public thì phương thức mở rộng có thể là public, protected hoặc private, tuy nhiên nếu phương thức trìu tượng là protected thì phương thức mở rộng chỉ có thể là protected hoặc public. Bạn không thể tạo ra một phương thức trìu tượng có phạm vi là private vì phương thức private không thể được kế thừa. Một điều cần để ý nữa là các phương thức mở rộng phải có cùng số tham số với định nghĩa của phương thức trìu tượng. Nếu một lớp có một phương thức trìu tượng thì lớp đó phải là lớp trìu tượng. Tuy nhiên, một lớp trìu tượng có thể không có phương thức trìu tượng. Phần này khá là trìu tượng nên chúng ta cần có những ví dụ cụ thể, chúng ta cùng xem sơ đồ UML các lớp dưới đây về ví dụ các hình:

Sơ đồ UML trong ví dụ về lớp trìu tượng

Chúng ta tạo ra một file abstract.php trong thư mục OOP với nội dung như sau:

<!doctype html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <title>Ví dụ về lớp trìu tượng và phương thức trìu tượng</title>
   <link rel="stylesheet" href="style.css">
</head>
<body>
    <?php
    #------------ ĐỊNH NGHĨA CLASS ----------------------#
    /* Định nghĩa lớp trìu tượng Shape
     * Lớp Shape không có thuộc tính
     * Lớp Shape có 2 phương thức trìu tượng:
     * - getArea()
     * - getPerimeter()
    */
    abstract class Shape {
        abstract protected function getArea();
        abstract protected function getPerimeter();
    }

    /* Định nghĩa lớp Triangle
     * Lớp Triangle có 2 thuộc tính:
     * - private $_sides (array)
     * - private $_perimeter (number)
     * Lớp Triangle có 3 phương thức:
     * - _ _construct()
     * - getArea()
     * - getPerimeter()
     */
    class Triangle extends Shape {
        private $_sides = array();
        private $_perimeter = NULL;

        function __construct($s0 = 0, $s1 = 0, $s2 = 0) {
            $this->_sides[] = $s0;
            $this->_sides[] = $s1;
            $this->_sides[] = $s2;

            // Tính toán và thiết lập chu vi hình tam giác
            $this->_perimeter = array_sum($this->_sides);
        }

        // Phương thức tính diện tích hình tam giác từ chu vi và các cạnh
        public function getArea() {
            return (SQRT(($this->_perimeter/2) * (($this->_perimeter/2) - $this->_sides[0]) * (($this->_perimeter/2) - $this->_sides[1]) * (($this->_perimeter/2) - $this->_sides[2])));
        }

        // Phương thức lấy chu vi hình tam giác
        public function getPerimeter() {
            return $this->_perimeter;
        }

    }

    // Thiết lập các cạnh hình tam giác
    $side1 = 5;
    $side2 = 10;
    $side3 = 13;

    echo "<h2>Tạo tam giác với 3 cạnh $side1, $side2, $side3</h2>";
    $t = new Triangle($side1, $side2, $side3);

    echo '<p>Diện tích hình tam giác ' . $t->getArea() . '</p>';
    echo '<p>Chu vi hình tam giác ' . $t->getPerimeter() . '</p>';

    unset($t);
    ?>
</body>
</html>

Trong ví dụ trên chúng ta đã tạo ra một lớp trìu tượng Shape chung cho rất nhiều hình và tạo ra lớp Triangle định nghĩa cụ thể về hình tam giác. Ví dụ cũng sử dụng các phương thức trìu tượng, khi mở rộng các phương thức này, phạm vi truy cập phải bằng hoặc rộng hơn, trong class Triangle, các phương thức getArea, getPerimeter có phạm vi là public rộng hơn so với trong phương thức trìu tượng là protected. Kết quả của ví dụ:

Ví dụ về lớp trìu tượng và phương thức trìu tượng

2. Interface

Interface giống như các lớp trìu tượng, định nghĩa các dấu hiệu cho phương thức, mặc định tên interface bắt đầu bởi chữ i. Tất cả các phương thức trong một interface phải là public. Các interface chỉ có phương thức, nó không bao giờ có thuộc tính. Để kết hợp một lớp với một phương thức sử dụng toán tử implements trong định nghĩa lớp:

class Someclass implements iSomething {}

Class này phải định nghĩa tất cả các phương thức được liệt kê trong interface nếu không một lỗi nghiêm trọng sẽ phát sinh. So sánh giữa abstract class và interface: sự khác nhau là rất nhỏ, abstract class có thể được mở rộng bởi một lớp, một interface không được kế thừa bởi một class mà chúng ta có thể coi nó là một cách để định nghĩa tổng thể đối tượng. Một interface thiết lập một thỏa thuận về các chức năng mà một class phải có. Một cách khác để nói về sự khác nhau giữa abstract class và interface là abstract class có quan hệ "is a" với lớp mở rộng từ nó còn interface không có quan hệ "is a". Trong ví dụ tiếp theo, chúng ta sẽ tạo ra một interface cho chức năng CRUD tiêu chuẩn.

<!doctype html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <title>Ví dụ về interface</title>
   <link rel="stylesheet" href="style.css">
</head>
<body>
    <?php
    /* Định nghĩa interface CRUD với 4 phương thức:
     * - create()
     * - read()
     * - update()
     * - delete()
     */
    interface iCrud {
        public function create($data);
        public function read();
        public function update($data);
        public function delete();
    }

    /* Lớp User implements interface iCrud
     * Lớp User có hai thuộc tính
     * - private $_userId
     * - private $_username
     * Lớp có 4 phương thức của interface và một phương thức khởi tạo
     */
    class User implements iCrud {
        private $_userId = NULL;
        private $_username = NULL;

        function __construct($data) {
            $this->_userId = uniqid();
            $this->_username = $data['username'];
        }

        function create($data) {
            self::__construct($data);
        }

        function read() {
            return array('userId' => $this->_userId, 'username' => $this->_username);
        }

        function update($data) {
            $this->_username = $data['username'];
        }

        public function delete() {
            $this->_username = NULL;
            $this->_userId = NULL;
        }

    }

    $user = array('username' => 'xman');

    echo "<h2>Creating a New User</h2>";
    $me = new User($user);

    $info = $me->read();
    echo "<p>The user ID is {$info['userId']}.</p>";

    $me->update(array('username' => 'gman'));

    $info = $me->read();
    echo "<p>The user name is now {$info['username']}.</p>";

    $me->delete();
    unset($me);
    ?>
</body>
</html>

3. Trait

Trait được hỗ trợ từ PHP 5.4 được sử dụng để giải quyết một vấn đề trong các ngôn ngữ lập trình hướng đối tượng như PHP, nó chỉ cho phép kế thừa duy nhất. Ví dụ, khi bạn thiết kế một website với nhiều các class như User, Page, ContactForm... trong khi phát triển website, cần một công cụ debug để in ra các thông tin về đối tượng.

function dumpObject() {
  // Print out the information.
}

Bạn có thể thêm định nghĩa này vào mỗi lớp, nhưng không cần thiết phải dư thừa như vậy. Thông thường khi bạn muốn một phương thức cho nhiều các class, kế thừa là một giải pháp. Tuy nhiên, trong PHP mỗi class chỉ có thể kế thừa duy nhất một lớp và không có một lớp cha dùng chung như vậy, và giải pháp là trait. Để tạo ra một trait sử dụng từ khóa trait, tiếp theo là tên và định nghĩa:

trait tSomeTrait {
  // Attributes
  function someFunction() {
    // Do whatever.
  }
}

Thông thường, tên trait sẽ bắt đầu bằng chữ t. Giống như một abstract class và interface, trait không thể tạo ra đối tượng. Thay vào đó, bạn thêm trait vào class thông qua từ khóa use trong định nghĩa class:

class SomeClass {
  use tSomeTrait;
  // Rest of class.
}

Về bản chất, nó chỉ đơn giản là chèn vào một đoạn mã PHP bên ngoài để có thể sử dụng đoạn mã này. Khi bạn tạo ra một đối tượng SomeClass, đối tượng sẽ có phương thức someFunction():

$obj = new SomeClass();
$obj->someFunction();

Trong ví dụ thực hành tiếp theo chúng ta sẽ tạo ra một trait về debug:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Ví dụ về Trait</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <?php 
    /* tDebug trait.
     * Trait định nghĩa một phương thức: dumpObject():
     */
    trait tDebug {
        public function dumpObject() {
            $class = get_class($this);
            $attributes = get_object_vars($this);
            $methods = get_class_methods($this);

            echo "<h2>Information about the $class object</h2>";

            echo '<h3>Attributes</h3><ul>';
            foreach ($attributes as $k => $v) {
                echo "<li>$k: $v</li>";
            }
            echo '</li></ul>';

            echo '<h3>Methods</h3><ul>';
            foreach ($methods as $v) {
                echo "<li>$v</li>";
            }
            echo '</li></ul>';
        }
    }

    /* Khai báo lớp Rectangle
     * Lớp có hai thuộc tính: width và height.
     * Lớp có năm phương thức:
     * - _ _construct()
     * - setSize()
     * - getArea()
     * - getPerimeter()
     * - isSquare()
     */
    class Rectangle {
        use tDebug;

        public $width = 0;
        public $height = 0;

        function _ _construct($w = 0, $h = 0) {
            $this->width = $w;
            $this->height = $h;
        }

        function setSize($w = 0, $h = 0) {
            $this->width = $w;
            $this->height = $h;
        }

        function getArea() {
            return ($this->width * $this->height);
        }

        function getPerimeter() {
            return ( ($this->width + $this->height) * 2 );
        }

        function isSquare() {
            if ($this->width == $this->height)
                return true;
            } else {
                return false;
            }
        }
    }

    // Tạo một đối tượng từ class Rectangle
    $r = new Rectangle(42, 37);

    // In ra thông tin đối tượng hình chữ nhật thông qua sử dụng phương thức của Trait
    $r->dumpObject();

    unset($r);
    ?>
</body>
</html>

Kết quả, mọi lớp sử dụng trait tDebug đều có thể sử dụng phương thức dumpObject(). Truy cập đường dẫn http://oop.dev/trait.php chúng ta được như sau:

Ví dụ về Trait trong lập trình hướng đối tượng PHP

4. Type Hinting

Type hinting là một hoạt động lập trình nhận diện dạng giá trị mong muốn, ví dụ dạng giá trị một hàm mong đợi cho một tham số. Để thực hiện type hinting đặt dạng class mong muốn trước tên biến tham số.

class SomeClass {
  function doThis(OtherClass $var) {
  }
}

Nếu tham số truyền cho phương thức doThis() không phải là dạng OtherClass hoặc một lớp mở rộng từ OtherClass, PHP sẽ sinh ra một lỗi nghiêm trọng:

class OtherClass {}
$some = new SomeClass();
$other = new OtherClass();
$some->doThis($other);
$some->doThis($some);

Chúng ta cùng xem ví dụ sau đây về Type Hinting:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Ví dụ về Type Hinting</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <?php
    #------------ ĐỊNH NGHĨA CLASS ----------------------#
    /* Class Department.
     * Lớp này có hai thuộc tính: name và employees[].
     * Lớp này có hai phương thức:
     * - __construct()
     * - addEmployee()
     */
    class Department {
        private $_name;
        private $_employees;
        function __construct($name) {
            $this->_name = $name;
            $this->_employees = array();
        }
        function addEmployee(Employee $e) {
            $this->_employees[] = $e;
            echo "<p>{$e->getName()} has been added to the {$this->_name} department.</p>";
        }
    }

    /* Class Employee.
     * Lớp này chưa một thuộc tính: name.
     * Lớp này chứa 2 phương thức:
     * - __construct()
     * - getName()
     */
    class Employee {
        private $_name;
        function __construct($name) {
            $this->_name = $name;
        }
        function getName() {
            return $this->_name;
        }
    }
    #------------ KẾT THÚC ĐỊNH NGHĨA CLASS ----------------------#

    // Tạo một phòng mới: Quản lý nhân sự
    $hr = new Department('Phòng Nhân sự');

    // Tạo hai nhân viên mới
    $e1 = new Employee('Nguyễn Văn A');
    $e2 = new Employee('Nguyễn Văn B');

    // Thêm hai nhân viên này vào Phòng Nhân sự
    $hr->addEmployee($e1);
    $hr->addEmployee($e2);

    unset($hr, $e1, $e2);
    ?>
</body>
</html>

5. Namespace

Được thêm vào từ phiên bản PHP 5.3, namespace cung cấp giải pháp cho các vấn đề cơ bản của lập trình hướng đối tượng như việc quản lý các class, thêm các khai báo của lập trình viên khác cũng như là các thư viện bên thứ ba, xung đột có thể xảy ra nếu nhiều lớp có cùng tên. Namespace ngăn chặn xung đột tên bằng cách tổ chức code trong các nhóm. PHP sử dụng giải pháp tạo ra các cấu trúc thư mục trên máy tính cho namespace do không thể đặt hai file tên giống nhau trong cùng một thư mục. Để định nghĩa một namespace, chúng ta tạo ra một file và sử dụng từ khóa namespace trong file đó.

namespace SomeNamespace;

Cần chú ý, đây phải là dòng đầu tiên trong file mã nguồn PHP và kể cả file có chứa mã HTML. Bất đoạn code nào tiếp theo sau sẽ được tự động đặt vào trong namespace này: Namespace có thể có các namespace con là các cấp trong thư mục và được phân cách bởi dấu \:

namespace MyUtilities\UserManagement;
class Login {
}

Sau khi định nghĩa namespace chúng ta có thể tham chiếu đến các namespace thông qua các dấu \:

require('SomeNameSpace.php');
$obj = new \SomeNameSpace\SomeClass();

hay

require('MyUtilities\User\User.php');
$obj = new \MyUtilities\User\Login();

CÁC BÀI VIẾT KHÁC

FirebirD

Đam mê Toán học, Lập trình. Sở thích chia sẻ kiến thức, Phim hài, Bóng đá, Cà phê sáng với bạn bè.

Callback trong PHP là gì?

Khái niệm Lambda và Closure trong PHP

1 Bình luận trong "Lập trình hướng đối tượng trong PHP - Phần 3 Đa dạng hóa trong kế thừa"

  1. Doanh

    1 year ago

    Phản hồi
    Bài viết rất đầy đủ, cảm ơn bạn.

Thêm bình luận