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

Chào các bạn, một ngày xấu trời nọ mình ngồi cào phím, mình nghĩ nếu đã bấm vào bài này thì chắc có lẽ bạn đã từng một lần gõ Lập trình hướng đối tượng trên Google hoặc đã suy nghĩ đến nó nhưng còn đang mông lung chưa biết sẽ bắt đầu như thế nào hoặc vì một lí do nào đó thì cũng coi như duyên số đã đưa ta đến với nhau nên hãy đọc tiếp nhé.

Lập trình hướng đối tượng là gì?

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"). OOP được xem là giúp tăng năng suất, đơn giản hóa độ phức tạp khi bảo trì cũng như mở rộng phần mềm bằng cách cho phép lập trình viên tập trung vào các đối tượng phần mềm ở bậc cao hơn... - Wiki said

Nói một cách dễ hiểu, đó chính là việc nắm bắt những hành động, đặc tính của những đối tượng ngoài tự nhiên và đem vào lập trình trở thành một đối tượng ảo trong ngôn ngữ lập trình.

Nội dung hướng đến trong bài

  • 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()
  • 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 PSR2
  • Các phương pháp thiết kế hướng đối tượng (SOLID).

Các đặc điểm cơ bản của lập trình hướng đối tượng

Lập trình hướng đối tượng có 4 tính chất cơ bản

1. Tính trừu tượng (Abstraction)

Định ra một quy định cơ bản, và yêu cầu bất kể một lớp nào, hoặc phương thức nào khi muốn làm việc với nó, thì buộc phải định nghĩa theo các quy tắc mà nó đã đặt ra. Tuy nhiên, vì lớp trừu tượng vẫn được xem là một lớp. Thế nên, ngoài chức năng quy định lớp trừu tượng ra, thì nó vẫn có thể khởi tạo các thuộc tính hoặc phương thức khác để phục vụ cho việc sử dụng của những phương thức kế thừa nó. Từ những đối tượng giống nhau, bạn có thể trừu tượng hóa thành một lớp. Tính trừu tượng cho phép bạn loại bỏ tính chất phức tạp của đối tượng bằng cách chỉ đưa ra các thuộc tính và phương thức cần thiết của đối tượng trong lập trình.

<?php
	abstract class smartPhone{
		public $touch_screen;
		public $physical_button;
		public $camera;
		
		public function setData($touch_screen = null, $physical_button = null, $camera = null){
			$this->touch_screen = $touch_screen;
			$this->physical_button = $physical_button;
			$this->camera = $camera;
		}
	}

	class iPhone extends smartPhone{
		public function __construct($screen, $button, $camera){
				$this->setData($screen, $button, $camera);
		}
		public function outputData(){
			echo $this->touch_screen;
			echo "<br />";
			echo $this->physical_button;
			echo "<br />";
			echo $this->camera;
		}
	}

	$iPhone5s = new iPhone("4 inch", 1, "8 megapixel");
	$iPhone5s -> outputData();
    
/*Output: 
4 inch
1
8 megapixel
*/

2. Tính đa hình (Polymorphism)

Người lập trình có thể định nghĩa một đặc tính (chẳng hạn thông qua tên của các phương thức) cho một loạt các đối tượng gần nhau nhưng khi thi hành thì dùng cùng một tên gọi mà sự thi hành của mỗi đối tượng sẽ tự động xảy ra tương ứng theo đặc tính của từng đối tượng mà không bị nhầm lẫn.

<?php
	abstract class smartPhone{
		public $touch_screen;
		public $physical_button;
		public $camera;
		
		public function setData($touch_screen = null, $physical_button = null, $camera = null){
			$this->touch_screen = $touch_screen;
			$this->physical_button = $physical_button;
			$this->camera = $camera;
		}
	}
	class iPhone extends smartPhone{
		public function __construct($screen, $button, $camera){
				$this->setData($screen, $button, $camera);
		}
		public function outputData(){
			echo $this->touch_screen;
			echo "<br />";
			echo $this->physical_button;
			echo "<br />";
			echo $this->camera;
		}
	}
	class Samsung extends smartPhone{
		public function __construct($screen, $button, $camera){
				$this->setData($screen, $button, $camera);
		}
		public function outputData(){
			echo $this->touch_screen;
			echo "<br />";
			echo $this->physical_button;
			echo "<br />";
			echo $this->camera;
		}
	}
	$iPhone5s = new iPhone("4 inch", 1, "8 megapixel");
	$SamsungS6 = new Samsung("5.1 inch", 1, "16 megapixel");
	$iPhone5s->outputData();
	echo "<br />";
	$SamsungS6 -> outputData();
    
 /*Output: 
4 inch
1
8 megapixel
5.1 inch
1
16 megapixel
*/

3. Tính đóng gói (Encapsulation)

Trong trường hợp một đối tượng thuộc lớp cần thực hiện một chức năng không nằm trong khả năng vì chức năng đó thuộc về một đối tượng thuộc lớp khác, thì nó sẽ yêu cầu đối tượng đó đảm nhận thực hiện công việc. Một đối tượng sẽ không được truy xuất trực tiếp vào thành phần dữ liệu của đối tượng khác cũng như không đưa thành phần dữ liệu của mình cho đối tượng khác một cách trực tiếp. Tất cả mọi thao tác truy xuất vào thành phần dữ liệu từ đối tượng này qua đối tượng khác phải được thực hiện bởi các phương thức (method) của chính đối tượng chứa dữ liệu.

<?php
	abstract class smartPhone{
		public $touch_screen;
		public $physical_button;
		public $camera;
		protected $Os;
		private $account;
		
		public function setData($touch_screen = null, $physical_button = null, $camera = null, $Os = null, $account = null){
			$this->touch_screen = $touch_screen;
			$this->physical_button = $physical_button;
			$this->camera = $camera;
			$this->Os = $Os;
			$this->account = $account;
		}
	}

	class iPhone extends smartPhone{
		public function __construct($screen, $button, $camera, $Os, $account){
				$this->setData($screen, $button, $camera, $Os, $account);
		}
		public function outputData(){
			echo $this->touch_screen;
			echo "<br />";
			echo $this->physical_button;
			echo "<br />";
			echo $this->camera;
			echo "<br />";
			echo $this->Os;
			echo "<br />";
			echo $this->account;
		}
	}

	$iPhone5s = new iPhone("4 inch", 1, "8 megapixel", "10.1.1", "[email protected]");
	$iPhone5s -> outputData();

/*Output: 
4 inch
1
8 megapixel
10.1.1
NOTICE Undefined property: iPhone::$account on line number 31
*/

4. Tính kế thừa (Inheritance)

Tính kế thừa là khả năng cho phép ta xây dựng một lớp mới dựa trên các định nghĩa của một lớp đã có. Lớp đã có gọi là lớp Cha, lớp mới phát sinh gọi là lớp Con và đương nhiên kế thừa tất cả các thành phần của lớp Cha, có thể mở rộng công năng các thành phần kế thừa cũng như bổ sung thêm các thành phần mới.

<?php
	abstract class smartPhone{
		public $touch_screen;
		public $physical_button;
		public $camera;
		
		public function setData($touch_screen = null, $physical_button = null, $camera = null){
			$this->touch_screen = $touch_screen;
			$this->physical_button = $physical_button;
			$this->camera = $camera;
		}
	}

	class iPhone extends smartPhone{
		public $touchID = "YES";

		public function __construct($screen, $button, $camera){
				$this->setData($screen, $button, $camera);
		}
		public function outputData(){
			echo $this->touch_screen;
			echo "<br />";
			echo $this->physical_button;
			echo "<br />";
			echo $this->camera;
			echo "<br />";
			echo $this->touchID;
		}
	}

	$iPhone5s = new iPhone("4 inch", 1, "8 megapixel");
	$iPhone5s->outputData();
    
/*Output: 
4 inch
1
8 megapixel
YES
*/

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

Để có thể phân biệt được Abstract Class và Interface, trướng tiên ta cần biết chúng là gì.

  • Abstract Class: được xem như một class cha cho tất cả các Class có cùng bản chất.
  • Interface: được xem như một mặt nạ cho tất cả các Class cùng cách thức hoạt động nhưng có thể khác nhau về bản chất. Ví dụ:
    • Abstract Class alcohol có các lớp con whisky, wine
    • Abstract Class soda có các lớp con pepsi, sprite
    • Interface gồm có drink, drunk Khá là phê pha nhưng mà ta vẫn có thể thấy rõ rằng whiskypepsi sẽ có chung Interface là drink, còn pepsi thì không thể kế thừa thêm Abstract Class alcohol được (Nếu được thì nên tập dần để trở về đúng bản chất nhé 😄)

Từ ví dụ trên ta có thể nhận ra cả 2 đều là "bản thiết kế" cho các lớp dẫn xuất, do đó chúng chỉ chứa các khai báo Properties và Method mà không quan tâm bên trong thực hiện những gì. Nhưng cụ thể thì Abstract Class là "bản thiết kế" cho Class còn Interface là "bản thiết kế" cho Method. Tuy nhiên sẽ có những khác biệt được mình liệt kê dưới đây:

Abstract Class Interface
Đa kế thừa extends một và chỉ một Abstract Class implements nhiều Interface
Khởi tạo Có thể chứa thêm các Method đã được triển khai hoặc các biến, hằng Chỉ chứa phương thức Abstract
Thực thi Chỉ cần thực hiện những Method và Properties được khai báo trừu tượng (có từ khóa abstract) Bắt buộc phải thực hiện toàn bộ những Method và Properties đã khai báo
Đóng gói Bắt buộc khai báo (public, protected) và bắt buộc có từ khóa abstract trong các Method và Properties được khai báo trừu tượng Mặc định coi là public
Tích hợp Phải tìm hết tất cả các lớp nào đang thực thi interface và định nghĩa thực thi cho phương thức mới Có thể lựa chọn cung cấp những thực thi mặc định và do đó tất cả các code đang tồn tại vẫn chạy bình thường
Tốc độ Nhanh hơn Cần nhiều thời gian để tìm phương thức thực tế tương ứng với lớp

Chú ý: Việc implements nhiều Interface có phương thức cùng tên nhưng khác số lượng biến truyền vào sẽ gây lỗi "Fatal error"!

Thế nào là một hàm static

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.

public static function exampleFunction ()
{
//
}

Chú ý: 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()

Nhìn chung thì cả selfstaticđều dùng để gọi các thành phần tĩnh trong đối tượng, nhưng nếu chỉ đơn thuần như trong nội bộ class thì cả 2 cách đều cho kết quả giống nhau.

<?php 
class smartPhone
{
    private static $phone = 'iphone';
    public static function getPhone()
    {
        echo self::$phone;
        echo '<br />';
        echo static::$phone;
    }
}

smartPhone::getPhone();

/*Output:
iphone
iphone*/

Tuy nhiên để có thể phân biệt cụ thể, ta thử thực hiện:

<?php 
class smartPhone
{
    public static function getSelf()
    {
        return new self;
    }
    public static function getStatic()
    {
        return new static;
    }
}
class iPhone extends smartPhone
{
}
echo get_class(iPhone::getSelf()); 
echo '<br />';
echo get_class(iPhone::getStatic()); 
/*Output:
smartPhone
iPhone
*/

Từ đây có thể đưa ra kết luận:

static::method() self::method()
Truy xuất đến đối tượng hiện tại Truy xuất đến class khai báo nó

Tạm kết bài

Phần 1 trong series đến đây là hết. Vì bài viết khá dài nên rất cám ơn mọi người đã theo dõi và hẹn gặp lại ở Phần 2 với những nội dung:

  • Trait
  • Namespaces
  • magic functions
  • các quy tắc trong PSR2