Lập trình hướng đối tượng trong PHP(Phần 1)

Lập trình hướng đối tượng (OOP) là một trong những kỹ thuật lập trình rất quan trọng hiện nay. Nó được áp dụng ở hầu hết các ứng dụng thực tế xây dựng tại các doanh nghiệp. Các lập trình viên đa phần đã được học về lập trình hướng đối tượng ở trường đại học nhưng các nguyên lý cơ bản của lập trình hướng đối tượng đôi khi lại không nắm rõ dẫn đến sử dụng sai, không đúng triết lý của lập trình hướng đối tượng. Trên đây mình cũng có một chút chia sẻ về một số kiến thức về Lập trình hướng đối tượng nhằm giúp các bạn có được một cái nhìn tổng quát về OOP cũng như cách áp dụng nó. Vậy lập trình hướng đối tượng là gì? Theo wikipedia :

lập trình hướng đối tượng là một mẫu hình lập trình dựa trên khái niệm "công nghệ đối tượng", mà trong đó, đối tượng chứa đựng các dữ liệu, trên các trường, thường được gọi là các thuộc tính; và mã nguồn, được tổ chức thành các phương thức. Phương thức giúp cho đối tượng có thể truy xuất và hiệu chỉnh các trường dữ liệu của đối tượng khác, mà đối tượng hiện tại có tương tác (đối tượng được hỗ trợ các phương thức "this" hoặc "self"). Trong lập trình hướng đối tượng, chương trình máy tính được thiết kế bằng cách tách nó ra khỏi phạm vi các đối tượng tương tác với nhau. Ngôn ngữ lập trình hướng đối tượng khá đa dạng, phần lớn là các ngôn ngữ lập trình theo lớp, nghĩa là các đối tượng trong các ngôn ngữ này được xem như thực thể của một lớp, được dùng để định nghĩa một kiểu dữ liệu.

Để dễ hiểu, lập trình hướng đối tượng là một kỹ thuật lập trình cho phép lập trình viên tạo ra các đối tượng trong code trừu tượng hóa các đối tượng thực tế trong cuộc sống.

Mục lục

Phần 1

  • Các đặc điểm cơ bản của lập trình hướng đối tượng. Chúng được thể hiện như thế nào trong PHP
  • Sự khác biệt giữa Abstract Class và Interface.
  • Thế nào là một hàm static. Phân biệt cách dùng từ khoá static::method() với self::method()

Phần 2

  • Thế nào là Trait
  • Thế nào là Namespaces
  • Thế nào là magic functions
  • Tìm hiểu về các quy tắc trong PSR-2

Phần 3

  • Các phương pháp thiết kế hướng đối tượng (SOLID).

Trong bài đầu tiên, mình sẽ chia sẻ về 3 vấn đề đó là: Các đặc điểm cơ bản của lập trình hướng đối tượng. Chúng được thể hiện như thế nào trong PHP, Sự khác biệt giữa Abstract Class và Interface, Thế nào là một hàm static. Phân biệt cách dùng từ khoá static::method() với self::method()

Nội dung

1. Các đặc điểm cơ bản của lập trình hướng đối tượng. Chúng được thể hiện như thế nào trong PHP?

Trong lập trình hướng đối tượng, sẽ có 4 tính chất sau: Tính trừu tượng (abstraction) trong lập trình hướng đối tượng giúp giảm sự phức tạp thông qua việc tập trung vào các đặc điểm trọng yếu hơn là đi sâu vào chi tiết.

  • Ví dụ khi một lập trình viên tạo một lớp (class) dùng đại diên cho các tài khoản tiền gửi ngân hàng của các khách hàng và đặt tên cho lớp này là BankAccount. Lớp này có hai thuộc tính là $balance và $interest dùng để lưu dữ liệu số tiền dư và lãi suất tiền gửi của tài khoản.
class BankAccount
{
   public $balance;
   public $interest;
}
  • Tiếp theo lập trình viên này thêm các phương thức gửi tiền (deposit) và rút tiền (withdraw) :
class BankAccount
{
    public $balance;
    public $interest;
    
    public function deposit ($amount) {
        //code
    }
    public function withdraw ($amount) {
        //code
    }
}

Với tính trừu tượng (abstraction) thì toàn bộ sự phức tạp của việc xử lý quá trình gửi tiền và rút tiền sẽ được thực hiện trong 2 phương thức depositwithdraw. Các lập trình viên không cần phải quan tâm tới sự phức tạp (hay nội dung chi tiết) của việc xử lý các công việc gửi tiền và rút tiền trên mà chỉ cần biết mục đích của từng phương thức là gì.

Dưới đây là một cách thực hiện (implementation) của phương thức deposit:

//nap tien vao tai khoan
	public function deposit ($amount) {
	if ($amount < 50000) { //so tien toi thieu
		return "Error! The minumun amount is 50";
	}
	if ($amount > 100000000) { //tai khoan nay cho phep ban nap toi da 100 trieu 1 lan
		return "Error! You exceed the maximum amount, please upgrade your account";
	}
	$balance += $amount // tang so du tai khoan 
}

Vậy với tính trừu tượng thì lập trình viên chỉ cần quan tâm tới mục đích của phương thức deposit là để nạp tiền vào tài khoản. Toàn bộ chi tiết của quy trình xử lý gửi tiền sẽ được thực hiện ở bên trong phương thức deposit.

Tính kế thừa (Inheritance) trong lập trình hướng đối tượng cho phép một lớp (class) có thể kế thừa các thuộc tính và phương thức từ các lớp khác đã được định nghĩa. Lớp được kế thừa còn được gọi là lớp cha và lớp kế thừa được gọi là lớp con. Trong PHP việc kế thừa được thực hiện thông qua sử dụng từ khóa extends. Ở ví dụ dưới đây lớp Animal được gọi là lớp cha và lớp Bird được gọi là lớp con.

<?php
/**
* 
*/
class Animal
{
	// thuoc tinh
	var $mat = '';
	var $mui = '';
	var $chan = '';

	function an() {
		//code
	}

	function ngu() {
		//code
	}
}
/**
* 
*/
class Bird extends Animal
{
	var $canh = '';

	function bay() {
		//
	}
}

Ở đây ta thấy thay vì khai báo lại thuộc tính và phương thức của class Animal trong class Bird, ta sẽ sử dụng tính kế thừa bằng từ khóa extends. Ngắn gọn lại dễ hiểu hơn rất nhiều phải không 😄 !!! Tính đa hình (polymorphism) trong lập trình hướng đối tượng cho phép các lớp con có thể viết lại (override) các thuộc tính hoặc phương thức từ lớp cha. VD:

<?php
/**
* 
*/
class Animal
{
	// thuoc tinh
	var $mat = '';
	var $mui = '';
	var $chan = '';

	function an() {
		//code
	}

	function ngu() {
		//code
	}
}
/**
* 
*/
class Bird extends Animal
{
	var $canh = '';

	public $mat = 'toi co mot doi mat';

	function bay() {
		//
	}
}

$animal = new Animal();
echo $animal->mat; //ket qua : ''
$bird = new Bird();
echo $bird->mat; // ket qua : toi co mot doi mat

Tính đóng gói (encapsulation) trong lập trình hướng đối tượng là tính chất không cho phép người dùng hay đối tượng khác thay đổi dữ liệu thành viên của đối tượng nội tại. Chỉ có các hàm thành viên của đối tượng đó mới có quyền thay đổi trạng thái nội tại của nó mà thôi. Các đối tượng khác muốn thay đổi thuộc tính thành viên của đối tượng nội tại, thì chúng cần truyền thông điệp cho đối tượng, và việc quyết định thay đổi hay không vẫn do đối tượng nội tại quyết định. Tính đóng gói thể hiện qua 3 mức độ truy cập :

  • Public : Đây là mức truy cập thoáng nhất bởi vì bạn có thể truy cập tới các phương thức và thuộc tính ở bất cứ đâu, dù trong nộ bộ của lớp hay ở lớp con hay cả bên ngoài lớp đều được.
  • Private: Các thuộc tính dữ liệu nhằm bảo vệ chúng, tránh sự truy cập tự do từ bên ngoài. Các thuộc tính này sẽ có những hàm SET và GET gán và lấy dữ liệu. Các phương thức trung gian tính toán trong nội bộ của đối tượng mà ta k muốn bên ngoài có thể can thiệp vào.
  • Protected: cho phép truy xuất nội bộ trong lớp đó và lớp kế thừa, riêng ở bên ngoài lớp sẽ không truy xuất đc. Mức protected thường được dùng cho những phương thức và thuộc tính có khả năng bị lớp con định nghĩa lại (overwrite).

2. Sự khác biệt giữa Abstract Class và Interface

Đây là 2 khái niệm cực kỳ quan trọng trong PHP. Và có lẽ không ít người đã nhầm tưởng về 2 khái niệm này.

Abstract (Lớp trừu tượng) : Chúng ta có thể hiểu đơn giản nó như một lớp cha cho tất cả các lớp con có cùng bản chất kế thừa nó. Chú ý là mỗi lớp con chỉ có thể thừa kế từ một lớp cha và chúng ta cũng không thể tạo được các đối tượng trực tiếp từ lớp cha. Các lớp này sẽ chứa các phương thức trừu tượng, các lớp khác khi kế thừa sẽ phải định nghĩa các phương thức ấy. VD:

<?php

abstract class animal {

   protected $vara = 'pa2() cua lop animal';

   abstract function pa();

   protected function pa2(){

       return $this->vara;
   }

}

class bird extends animal{

   public function pa(){

       echo 'pa() duoc khai bao abtract o lop animal';

   }

   public function pb(){

       echo $this->pa2();
   }

}

$s = new bird();
$s->pb();

Interface (Lớp giao diện) : Lớp này được xem như một mặt nạ cho tất cả các lớp cùng cách thức hoạt động nhưng có thể khác nhau về bản chất. Từ đó lớp dẫn xuất có thể kế thừa từ nhiều lớp Interface để bổ sung đầy để cách thức hoạt động của mình (đây gọi là đa kế thừa). Chú ý các bạn không được định nghĩa các phương thức trong lớp interface. VD:

interface a{
    public function pa();
}
interface b{
    public function pb();
}
class c{
    public function pc(){
        echo 'phuong thuc pc cua lop c';
    }
}
class d extends c implements a,b{
    public function pa(){
        echo 'phuong thuc pa duoc khai bao interface';
    }
    public function pb(){
        echo 'phuong thuc pb duoc khai bao interface';
    }
    public function pd(){
        echo 'phuong thuc pc cua lop d';
    }
}
$s = new d();
$s->pc();

Dưới đây mình xin nêu ra 1 số đặc điểm khác nhau giữa 2 khái niệm này.

3. Thế nào là một hàm static. Phân biệt cách dùng từ khoá static::method() với self::method()

Hàm static : Là hàm có thể truy cập mà không cần khởi tạo một đối tượng của class. Hàm này được khai báo trong class, có kèm theo từ khóa static. Trước từ khóa static có thể có một trong các cơ chế Public, Protected, Private. VD:

    public static function staticExample()
    {
       //
    }

Một số lưu ý với khi sử dụng hàm static:

  • Có thể truy cập không cần khởi tạo đối tượng.
  • Truy cập bằng từ khóa Self trong Class.
  • Self và static là đại diện cho Class
  • Trong hàm static không thể gọi hàm hoặc thuộc tính non-static. Nhưng hàm non-static có thể gọi hàm hoặc thuộc tính static do các thuộc tính và hàm static ở dạng toàn cục, được gọi mà không cần khởi tạo nên nếu bạn dùng từ khóa $this để gọi đến một hàm nào đó trong chính lớp đó thì sẽ bị báo sai.

Phân biệt cách dùng từ khoá static::method() với self::method() Ở trên ta có đề cập đến việc self và static đều dùng để gọi các thành phần tĩnh trong đối tượng. Vậy chúng có sự khác nhau như thế nào? Tại sao lại phải dùng 2 từ khóa. Chúng ta cùng theo dõi ví dụ sau:

    <?php
    class Monkey
    {
        protected static $legs = 4;

        public static function countLegs() {
             return self::$legs;
        }
    }

    class Human extends Monkey
    {
        protected static $legs = 2;
    }
    
    echo Human::countLegs() . "\n";

Bạn cần kết quả là 2 nhưng kết quả trả về lại là 4. Điều này xảy ra là bởi vì khi sử dụng self thì giá trị thuộc tính sẽ được tham chiếu về giá trị của thuộc tính trong lớp cha thay vì lớp hiện tại. (Monkey thay vì Human). Thay đổi lại 1 chút code như sau:

<?php
    class Monkey
    {
        protected static $legs = 4;

        public static function countLegs() {
            return static::$legs;
       }
    }

    class Human extends Monkey
    {
        protected static $legs = 2;
    }
    
    echo Human::countLegs() . "\n";

Kết quả là 2. Sự khác biệt đã rõ 😄 Vậy static nó sẽ đại diện cho chính đối tượng đang gọi đến nó. Na ná kiểu $this vậy. self thì khác, nó đại diện cho đối tượng khai báo nó. Các bạn hãy làm thử 1 vài ví dụ chắc chắn sẽ hiểu được điều này ^^!

Tạm kết

Trên đây là một số điều mình tổng hợp được về một số vấn đề về Lập trình hướng đối tượng trong PHP. Trong bài sau, mình sẽ đề cập đến Trait, Namespaces, magic functions, và các quy tắc trong PSR-2. Đây cũng là những kiến thức rất quan trọng trong lập trình PHP. Mong nhận được sự theo dõi và ủng hộ của các bạn. Tạm biệt! Phần 2