เราอาจเคยเรียนรู้การสร้างคลาส สร้างวัตถุ หรือออบเจ็กต์ หรือการเขียนโปรแกรมเชิงวัตถุ ( Object Oriented Programming) หรือที่เรียกสั้น ๆ ว่า OOP ด้วยภาษาอื่น ๆ แนวคิดนั้นก็ใช้ได้กับภาษา PHP เพียงแต่ไวยากรณ์ของภาษา และข้อกำหนดอาจจะต่างกัน เรามาดูกันว่า ภาษา PHP มีการเขียนโปรแกรมเชิงวัตถุอย่างไร โดยเฉพาะในแนวทางของภาษาที่มีลักษณะชนิดข้อมูลไดนามิก
Class
สิ่งที่จะประกอบร่างเป็นคลาสได้ จะประกอบด้วย สมาชิกหลัก ๆ คือ แอททริบิว(Attribute) หรือพร็อบเพอร์ตี(Property) และเมธอด(Method) หรือฟังก์ชัน (Function) การสร้างคลาสใน PHP จะต้องสร้างด้วยคีย์เวิร์ด "class" นำหน้าชื่อคลาสเสมอ
Code 1. Product.php
<?php
class Product {
private $id; //attribute
private $name; //attribute
public function getId(){//getter method
return $this->id;
}
public function setId($id){ //setter method
$this->id = $id;
}
public function getName(){ //getter method
return $this->name;
}
public function setName($name){ //setter method
$this->name = $name;
}
}
มีหลายสิ่งที่เหมือนกับภาษาอื่น ๆ เช่น ใช้ คำนำหน้าคลาส ว่า class (อักษรพิมพ์เล็กทั้งหมด) ใช้ ตัวควบคุมการเข้าถึงวัตถุ เช่น private ให้เข้าถึงได้เฉพาะคลาสตนเอง ออบเจ็กต์มองเห็นสมาชิกแบบ private นี้ไม่ได้ ส่วน public สามารถเข้าถึงได้ มองเห็นได้ตลอด ทั้งจากตัวเอง และออบเจ็กต์อื่น แต่ก็สิ่งที่สังเกตได้บางอย่างไม่เหมือนกับภาษาอื่น คือว่า
- การตั้งชื่อตัวแปรจะใช้ $ นำหน้า และไม่ต้องระบุชนิดข้อมูล เพราะ PHP จะเลือกให้เองเมื่อถูกกำหนดค่าให้ในครั้งแรก เช่น เมื่อกำหนด ให้มีค่าเป็น ตัวเลข PHP ก็จะกำหนดค่าให้ตัวแปรมีชนิดข้อมูลเป็นตัวเลข
- การกำหนดฟังก์ชัน ใช้ชื่อว่า function ทั้งฟังก์ชัน คืนค่า และไม่คืนค่า ซึ่งไม่เมื่อกับภาษาอื่น เช่น Java จะใช้ void กับกรณีไม่คืนค่า และถ้าคืนค่าจะใช้ ชนิดข้อมูลระบุไปเลย เช่น Integer
- การอ้างอิงสมาชิก ใช้ เครื่องหมาย -> แทนการใช้ เครื่องหมายจุด เมื่อเทียบกับภาษาอื่น ๆ เช่น ภาษา Java, C#
- การอ้างอิงตัวเอง (ออบเจ็กต์ตัวเอง) ใช้คีย์เวิร์ด $this เป็นการอ้างตัวแปรของอิงออบเจ็กต์ ซึ่งต่อไป จะมีการอ้างอิงตัวแปรของคลาส ก็จะใช้คีย์เวิร์ดที่ต่างกัน (ตัวแปรของออบเจ็กต์ ไม่เหมือนกับตัวแปรของคลาส)
แต่ดูดี ๆ มีข้อสังเกตอีกอย่างหนึ่งคือ ตัวเปิดคำสั่ง <?php แต่ไม่มีตัวปิด ?> เพราะนี้เป็นคำสั่ง php ล้วน ๆ ไม่มีภาษาอื่น ๆ มาปน จึงไม่จำเป็นต้องมีตัวปิด
ทดสอบสร้างวัตถุ
มาทดสอบกันเลยว่า คลาสที่สร้างก่อนหน้านี้ สร้างเป็นวัตถุได้อย่างไร ดังตัวอย่างการคลาส Product ที่เขียนในโปรแกรม ต่อไปนี้
Code 2. index.php
<?php
include 'Product.php';
$s1 = new Product();
$s1->setId(1);
$s1->setName("TV");
echo $s1->getId() . " : " . $s1->getName();
/* output
1 : TV
*/
ให้สังเกตว่า มีอะไรต่างกับภาษาทั่วไปบ้าง ดูเหมือนแทบหาที่ต่างกันได้น้อยมาก นอกจากไม่ต้องระบุชนิดข้อมูลของตัวแปรก่อน ซึ่งก็เท่านั้น เพราะตามธรรมดาของภาษาไม่แข็งแรงในชนิดข้อมูล ไม่ต้องระบุชนิดข้อมูลการใช้งานอยู่แล้ว
สิ่งที่ต่างกับภาษาอื่น ๆ อีกเล็ก ๆ น้อย ๆ คือ ในการเชื่อมอักษรใช้เครื่องหมาย จุด (.) แต่ภาษาอื่น มักใช้เครื่องหมาย บวก (+)
มาดูในรายละเอียดกัน การสร้างวัตถุ หรือออบเจ็กต์ นี้ ใช้ตัวแปรชื่อ $s1 เป็นชื่ออบเจ็กต์ ที่สร้างขึ้นมาจากคลาส Product การสร้างออบเจ็กต์ ใช้คีย์เวิร์ด new ซึ่งเหมือนกับภาษาอื่น ๆ ชื่อคลาส ต้องตามด้วยเครื่องหมาย วงเล็บ ซึ่งต่อไปจะเรียกว่า คอนสตรักเตอร์ (Constructor) ซึ่งก็เหมือนภาษาอื่น ๆ อีก ต่อมา เราใช้ชื่อออเจ็กต์ เรียกตัวแปรของออบเจ็กต์ ได้ เช่นการเรียก ตัวแปรของออบเจ็กต์ประเภทฟังก์ชัน setId( ) และ setName( ) โดยใส่ตัวแปรเข้าฟังก์ชันนี้ตามที่กำหนดไว้ในตอนสร้างคลาส และสุดท้าย พิมพ์ข้อมูล (echo) ผ่านตัวแปรของออบเจ็กต์ ประเภทฟังก์ชัน คือ getId( ) และ getName( ) ที่คืนค่าข้อมูล id และ name ออกมา จะเห็นว่าการสร้างออบเจ็กต์เป็นเรื่องธรรมดามาก แต่ตามประสบการณ์ ได้เคยเห็นนักศึกษา เมื่อสร้างเว็บไซต์ จะพบว่า มีน้อยมาที่จะเขียน PHP ในเชิงวัตถุ
สร้างฟังก์ชันไว้ใช้เอง
แทนที่เราจะให้ echo จากการเรียกคุณสมบัติของคลาสมาดูทุกครั้ง เรามาสร้างฟังก์ชันไว้ใช้เองกันดีกว่า เช่น เราต้องการอ่าน id และ name ของวัตถุ เราสร้างฟังก์ชันให้อ่านเพื่อแสดงค่าได้ ดังเช่น ฟังก์ชัน info( ) ต่อไปนี้ จะเขียนเพิ่มเติมลงในคลาส Product
Code 3. Product.php (ต่อ)public function info(){
echo $this->id . ":" .$this->name;
}
ในกรณีที่เราต้องการให้คืนค่า id และ name เพื่อว่าผู้ที่เรียกใช้ฟังก์ชันนี้ นำข้อมูลที่คืนค่าไปดำเนินการอื่น ๆ เช่น นำไปพิมพ์ค่าเอง คิดว่าจะเขียนได้อย่างไร
Overload
การสร้างฟังก์ชัน ที่ใช้ชื่อเดิม แต่มีตัวแปรเข้าไม่เหมือนกัน เรียกฟังก์ชันที่มีปรากฎการณ์เหล่านี้ว่า โอเวอร์โหลด (Overload) ภาษาทาง OOP โดยทั่วไปก็ทำกันได้หมด แต่ PHP ยังทำโดยตรงไม่ได้ (PHP 5.6) หรือกล่าวอีกอย่างว่า เป็นการทำโอเวอร์โหลดในแบบของ PHP
ดูตัวอย่าง การทำฟังก์ชัน info( ) ก่อนหน้านี้ แต่ปรับปรุงข้อมูลภายใน เพื่อรองรับข้อมูลเข้า ในจำนวนที่ต่าง ๆ กัน ดังเขียนใหม่ได้ว่า
Code 4. Product.php (ต่อ)public function info(){
$info = "";
foreach (func_get_args() as $arg) {
switch ($arg){
case 'id': $info .= $this->id; break;
case 'name': $info .= $this->name; break;
}
}
echo $info;
}
ฟังก์ชัน info( ) นี้ใช้ นับตัวแปรผ่าน func_get_args( ) ซึ่งเป็นตัวแปรในลักษณะอาร์เรย์ และใช้ตัววิ่งใน foreach เป็น $arg ของอาร์เรย์ ทำให้ฟังก์ชันนี้ รับตัวแปรที่จำนวนต่าง ๆ กันได้ คล้าย ๆ กับการเขียนฟังก์ชัน info($array = null) ที่รับตัวแปรเข้าเป็นอาร์เรย์ แต่มีค่าปริยายเป็น null (แปลว่า ค่าว่าง หรือค่าที่ยังไม่มีการอ้างอิงข้อมูลใด ๆ ในหน่วยความจำ) หรือหมายความว่า ถ้าต้องใช้งานไม่ใส่ตัวแปรอะไร ก็ให้ถือว่า ตัวแปร $array มีค่าเท่ากับ null ที่นี้มาดูการทดสอบใช้งานกัน
Code 5. index.php<?php
include 'Product.php';
$s1 = new Product();
$s1->setId(1);
$s1->setName("TV");
$s1->info("id", "name");
//print 1:TV
จากโปรแกรมนี้ เมื่อเรียกฟังก์ชัน info( ) โดยใส่ตัวแปร เข้าสองตัวคือ id และ name ตัวแปรสองตัวนี้จะถูกตรวจสอบ จากคำสั่ง switch ว่าเข้ากรณีใด เช่น การตรวจพบว่า ตัว id ตรงกับ case 'id' ซึ่งคือกรณีแรก ก็จะทำการ ต่อตัวอักษร ของตัวแปร $info ที่จากเดิมไม่มีอักษรอะไร ให้ทำการต่อกับตัวแปรของออบเจ็กต์ ชื่อ id หรือ $this->id ในตัวอย่างโปรแกรมนี้ ได้รวมทั้ง id และ name จึง ได้ผลการพิมพ์ค่าข้อมูลเป็นอักษรของ id และ name ออกมา
การใช้ __get( ) , __set( )
ฟังก์ชัน __get( ) และ __set( ) จัดเป็นฟังก์ชันมายากล (Magic Method) และจัดให้สองฟังก์ชันนี้คือการทำโอเวอร์โหลด ในความหมายโอเวอร์โหลดของ PHP มีความหมายที่ไม่เหมือนกับภาษาอื่น ๆ เพราะการทำโอเวอร์โหลดของ PHP คือสร้างคุณสมบัติแบบไดนามิก สองฟังก์ชันนี้สามารถสร้างคุณสมบัติแบบไดนามิกได้ โดยไม่ต้องสร้าง getter และ setter ให้ครบตามภาษา OOP ทั่วไป
สมมุติว่าต้องการสร้างคลาส Product ที่มีคุณสมบัติเหมือนกับคลาส Product ที่เคยสร้าง แต่ครั้งนี้ใช้ ฟังก์ชันมายากลแทน
Code 6. Product.php<?php
class Product{
private $data = array();
public function __set($name, $value){
$this->data[$name] = $value;
}
public function __get($name){
return $this->data[$name];
}
public function info(){
$info = "";
foreach($this->data as $data){
$info .= $data . " ";
}
return $info;
}
}
ฟังก์ชันมายากลนี้ ต้องประกาศเป็น public เท่านั้น และฟังก์ชันเหล่านี้ สามารถเข้าถึงสมาชิกที่เป็น private ได้ การใช้งานทำได้ง่าย เมื่อสร้างเป็นออบเจ็กต์ ($product) การกำหนดสมาชิกใหม่ เช่น id และ name สามารถกำหนดผ่านฟังก์ชันได้ตรงตรง โดยไม่ต้องอ้างอิงฟังก์ชันมายากลเลย ลองดูการใช้งานกัน
Code 7. index.php<?php
include 'Product.php';
$product = new Product();
$product->id = 1;
$product->name = "TV";
echo $product->info(); //out 1 TV
ฟังก์ชัน info( ) ของคลาสนี้ เป็นฟังก์ชันแบบคืนค่า ใช้คีย์เวิร์ด return ในช่วงท้ายของฟังก์ชัน ซึ่งฟังก์ชันนี้จะคืนค่า ข้อมูลอักษรของ ตัวแปร $info ซึ่งต่อไป ก็นำค่าข้อมูลที่คืนค่านี้ ไปใช้งานอย่างอื่นได้ เช่น นำไปพิมพ์ ด้วยคำสั่ง echo
คอนสตรักเตอร์ (Constructor)
สำหรับบคอนสตรักเตอร์ ตัวนี้ดูจะความแตกต่างกับภาษาอื่น ๆ เพราะใช้คำสำคัญว่า __construct( ) สังเกตว่าใช้ อักษรขีดเส้นใต้สองอันเรียงติดกัน
Code 8. Product.php<?php
class Product {
//$private $id;
//$private $name;
public function __construct($id, $name){
$this->id=$id;
$this->name = $name;
}
public info(){
return $this->id. ":". $this->name;
}
}
คอนสตรักเตอร์ นี้ รับตัวแปรเข้าสองตัว ตัวแรกเป็นการกำหนดค่าสมาชิก id และตัวต่อมากำหนดค่าสมาชิก name ให้สังเกตว่า สมาชิกมีคุณสมบัติ private สองตัว ถูกกำหนดให้ไม่ทำงาน ด้วยการใส่เครื่องหมาย “//” คลาสนี้ก็ยังทำงานได้ เพราะการกำหนดค่า $this ภายในคอนสตรักเตอร์จะเป็นการกำหนดค่าสมาชิกให้คลาสนี้อัตโนมัติ แต่ถ้าต้องการ ทำให้อยู่ในรูปมาตรฐาน ก็สมารถใส่ ค่า private ให้ทำงานปกติได้ โดยการเอาเครื่องหมาย “//” ออก
คอนสตักเตอร์นี้ทำโอเวอร์โหลดไม่ได้ หากทำโอเวอร์โหลด จะเป็นการทำโอเวอร์โหลด ในแบบฉบับ PHP ดังที่เคยยกตัวอย่างมา
ต่อมา ทดสอบกับไฟล์ index.php เพื่อทดสอบว่ายังทำงานได้เหมือนเดิมหรือไม่ แล้วทดสอบอีกครั้งกับโปรแกรมต่อไปนี้ (คิดว่ามีอะไรผิดพลาด ก็จะได้ผลเหมือนเดิม)
Code 9. index.php<?php
include 'Product.php';
$s1 = new Product(1, "TV");
echo $s1->info( ); //output : 1:TV
การสร้างคอนสตรักเตอร์ ด้วยวิธีที่ผ่านมา จะไม่ใช้ชื่อคลาสแทนชื่อคอนสตรักเตอร์ เหมือนภาษาอื่น ๆ แต่ PHP ก็อนุญาต ให้ใช้ชื่อคลาสแทนคอนสตรักเตอร์ได้ แต่วิธีนี้ ไม่ใช่วิธีทางหลัก เพราะ PHP จะอ้างถึง __construct เป็นหลัก
ในตัวอย่างที่ผ่านมา กรณีที่ไม่มีเขียนค่า private $id และ private $name ไว้เลย ปล่อยให้คอนสตรักเตอร์กำหนดค่า สมาชิกอัตโนมัติผ่าน การกำหนดค่าภายในคอนสตรักเตอร์ การอ่านค่าจากฟังก์ชัน info( ) จะต้องระบุชื่อสมาชิกที่ถูกต้องตรงกับค่าที่กำหนดภายในคอนสตรักเตอร์ ซึ่งยังมีวิธีอื่น ที่อ่านค่าได้โดยไม่จำเป็นต้องสร้างทราบชื่อล่วงหน้า ดูตัวอย่างต่อไปนี้
Code 10. Product.php<?php
class Product {
public function Product($id, $name){
$this->id=$id;
$this->name = $name;
}
public function info(){
$info = "";
$lastProperty = end($this);
foreach ($this as $key => $value) {
if($value==$lastProperty) $info .= $value;
else $info .= $value . " : ";
}
return $info;
}
}
จากตัวอย่างนี้ จะเห็นว่า มีการอ่านค่าสมาชิกของคลาส ผ่าน การวนซ้ำ foreach เหมือนกับการอ่านค่าของอาร์เรย์แบบมีคีย์ ของค่า $this นอกจากนั้นการตรวจสอบ ค่าสุดท้ายของอาร์เรย์ สามารถใช้ฟังก์ชัน end( ) ซึ่งจะใช้ได้กับทุกอาร์เรย์
สร้างดีสตรักเตอร์ (Destructor)
เมื่อสร้างคลาส จะมี คอนสตรักเตอร์ทำงานเป็นอันดับแรก เมื่อวัตถุไม่ถูกใช้งาน ดีสตรักเตอร์ก็ถูกเรียกใช้งานเป็นอันดับสุดท้าย คำสั่งเรียกนี้ เขียนด้วย __destruct( ) ลองใส่โปรแกรมข้างล่างนี้ลงในคลาสเดิมที่เคยทดสอบ และดูผลกันว่าเป็นอย่างไร
Code 11. Product.php (ต่อ)function __destruct( ) {
echo " Object Destroyed.";
}
แล้วรู้ได้อย่างไรว่า วัตถุไม่ถูกใช้งานแล้ว คำถามนี้น่าสนใจ เราเองดูเองได้ว่า ไม่มีส่วนใดของโปรแกรมอ้างอิงถึงวัตถุนี้แล้ว และเพื่อให้ชัดไปเลยว่าไม่ใช้งานวัตถุนี้แล้ว ก็ให้เขียน ไปเลยว่าไม่ใช้แล้ว โดยให้มีค่าเท่ากับ null
ค่าคงที่ของคลาส
แน่นอนเลยว่า เรารู้มาแล้วว่าสร้างค่าคงที่ใน PHP ด้วยการใช้คีย์เวิร์ด define เพื่อกำหนดค่าคงที่ เช่น กำหนดว่า DSN_MYSQL ให้เป็นชุดอักษร (ตัวแปรค่าคงที่ตามธรรมเนียมใช้อักษรตัวใหญ่หมดทุกตัว) ในการต่อเชื่อมกับฐานข้อมูล
define("DSN_MYSQL", ="mysql:host=localhost;dbname=MySQLDB;");
เราก็จะใช้ DSN_MYSQL เป็นชื่อที่ใช้อ้างอิงทั้งระบบ ซึ่งคือตัวแปรสากล (Global variable) นั้นเอง (ตรงข้ามกับตัวแปรท้องถิ่น (Local variable)) แต่การสร้างค่าคงที่ในคลาส จะต้องใช้คำสำคัญว่า const ค่าคงที่เมื่อประกาศค่าจะเปลี่ยนแปลงไม่ได้ เราสามารถสร้างค่าคงที่ และใช้งานค่านี้ได้อย่างไร
Code 12. Studentd.php<?php
class Student {
const ID = 123456789;
public $school = "South East";
public function info(){
$data = self::ID . ":" . $this->school;
return $data;
}
}
ทดสอบสร้างเป็นวัตถุ และทดลองเรียกฟังก์ชัน info( ) เพื่อผลการทำงานของค่าคงที่ ดังเขียนได้ว่า :
Code 13. index.php<?php
include("Student.php");
$student = new Student();
echo $student->info();
//Output: 123456789:South East
ทีนี้มาดูในรายละเอียดกัน ว่าใช้งานกันอย่างไร อย่างแรก
- ค่าคงที่เป็นธรรมเนียม ต้องนิยามใช้ชื่ออักษรพิมพ์ใหญ่
- ค่าคงที่นี้ไม่ต้องมีอะไรนำหน้า เช่น private หรือ public ใช้นำหน้าไม่ได้ แต่มีลักษณะเหมือน public
- ค่าคงที่ไม่ต้องมีอักษร $ นำหน้า
- การอ้างค่าคงที่ในคลาสใช้ self อ้างอิง
- การอ้างอิงนอกคลาสใช้ ชื่อคลาสนำหน้า
- และอ้างถึงตัวแปรค่าคงที่ใดใช้ :: นำหน้าชื่อคลาส หรือ self
ค่าสแตติก (Static)
การอ้างอิงถึงค่าสแตติก ในทางการเขียนโปรแกรมเชิงวัตถุ จะหมายถึงค่า ตัวแปรของคลาส (ไม่ใช่ตัวแปรของวัตถุ) ที่ทุก ๆ วัตถุที่มาจากคลาสเดียวกันนี้ใช้ร่วมกัน แต่ค่านี้เปลี่ยนแปลงได้ (ถึงตอนนี้ คงแยกความแตกต่างได้ว่า ไม่เหมือนกับค่าคงที่ตรงไหน) มาดูกันจริง ๆ ว่า ใช้งานตัวแปรชนิดนี้ เรียกผ่านคลาส ซึ่งตรงกันข้ามกับ ตัวแปรที่ไม่เป็นค่าสแตติก จะถือเป็นตัวแปรของวัตถุ ซึ่งจะเรียกผ่านวัตถุ
Code 14. Product.php<?php
class Product {
private static $nextID = 1;
private $id;
private $name;
public function __construct($name){
$this->name = $name;
$this->id = self::$nextID;
self::$nextID++;
}
public function getName(){
return $this->name;
}
public function setName($name){
$this->name = $name;
}
public function getId(){
return $this->id;
}
public function info(){
return $this->id . ":". $this->name;
}
}
นำคลาสนี้ มาทดสอบ การสร้างวัตถุ สองตัว โดยเก็บไว้ในอาร์เรย์ ชื่อ $products การสร้างวัตถุ สังเกตว่า มีตัวแปรเข้าตัวเดียว ตามที่ได้นิยามไว้ในคอนสตรักเตอร์ และนำค่าออบเจ็กต์ในอาร์เรย์ออกมาพิมพ์ จากการวนซ้ำของ foreach มีตัววิ่งชื่อ $item ที่ทำหน้าที่แทนออบเจ็กต์แต่ละตัวของอาร์เรย์ $products
Code 15.index.php<?php
include 'Product.php';
$products = array(new Product("TV"), new Product("Radio"));
foreach ($products as $item ){
echo $item->info() . "<br>";
}}
/*Output:
1:TV
2:Radio
*/
จะเห็นว่าการอ้างถึงการใช้เหมือนค่าคงที่ แต่ต่างกันเพียง ยังคงมี $ นำหน้าตัวแปร เช่น ในที่นี้ใช้ self->$nextId แล้วถ้าอ้างอิงนอกคลาส จะอ้างอิงอย่างไร (หากทดลองทดสอบ จะต้องแก้ จาก private เป็น public ก่อน) และจากตัวอย่างนี้ จะเห็นว่า เรานำมาประยุกต์ใช้ตัวนับอัตโนมัติให้แก่สมาชิกชื่อ id ได้ เพราะค่าสแตติก จะไม่ขึ้นอยู่กับวัตถุ แต่จะคงอยู่กับตัวคลาส
การสืบทอด
คุณสมบัติยอดเยี่ยมย่างหนึ่งใน OOP คือคุณสามารถสืบทอดคลาส และสร้างวัตถุใหม่อย่างสมบูรณ์ วัตถุใหม่สามารถรักษาทุกฟังก์ชันของคลาสที่สืบทอด หรือคลาสฐาน มาเป็นต้นแบบได้ การสืบทอดนี้จะใช้ คำสำคัญ extends เพื่อการสืบทอด เช่น ให้คลาส Radio สืบทอดจากคลาส Product จะเขียนได้ว่า
Code 16. Raido.php<?php
class Radio extends Product{
}
เรามาลองทดสอบกันว่า เราจะสร้าง วัตถุของคลาส Radio โดยเรียกคุณสมบัติมาจากคลาสฐาน (Product) ได้ดังตัวอย่างต่อไปนี้ (สังเกตว่า เราต้องรวมไฟล์ Radio.php มาด้วย และต้องต่อจากไฟล์ Product.php เพราะคลาส Radio เรียกใช้คลาส Product จึงต้องมีคลาส Product มาก่อน)
Code 17. index.php<?php
include 'Product.php';
include 'Radio.php';
$products = array(new Radio("Tarnin"), new Radio("Pana"));
foreach ($products as $item ){
echo $item->info() . "<br>";
}
การเรียกคอนสตรักเตอร์ที่สืบทอด
มีสิ่งที่ไม่เหมือนกับภาษาอื่น ๆ ในการเรียกคอนสตรักเตอร์จากคลาสที่สืบทอด ในภาษาอื่น ๆ เช่น Java คลาสที่สืบทอด จะเรียกคอนสตรักเตอร์ของคลาสที่สืบทอดก่อน หรือเกิดตามคลาสที่สืบทอด แต่สำหรับ PHP ไม่จำเป็นต้องเกิดตามคลาสที่สืบทอด ถ้าเขียนคอนสตรักเตอร์ของตนเอง
Code 18. Radio.php<?php
class Radio extends Product{
public function __construct(){
echo ("Constructor B");
}
ถ้าทดสอบ สร้างออบเจ็กต์ Radio เช่น $radio = new Radio( ) จะได้ออบเจ็กต์ $radio ที่ไม่มีค่าสแตติก ทำกำหนดค่า $nextId ติดมาด้วย วิธีการแก้คือ ต้องสร้างคอนสตรักเตอร์ตามแบบคลาส Product แล้วเรียกคอนสตรักเตอร์ของคลาส Product ดังตัวอย่างต่อไปนี้
Code 19. Radio.php<?php
class Radio extends Product {
public function __construct($name) {
echo "Constuctor Radio";
parent::__construct($name);
}
}
การเรียกในตัวอย่างที่ผ่านมา ใช้ parent:: เป็นตัวเรียกคอนสตรักเตอร์ของคลาสที่สืบทอด นอกจากนี้ถ้ามีการสืบทอดกันมาหลาย ๆ ทอด เราใช้ชื่อคลาสเรียกคอนสตรักเตอร์ได้ แทนการเรียก parent ดังตัวอย่างต่อไปนี้
Code 20. DigitalRadio.php<?php
class DigitalRadio extends Radio{
public function __construct($name) {
echo "Constuctor DigitalRadio";
Product::__construct($name);
}
}
โอเวอร์ไรด์
การสืบทอดกันของวัตถุ จากการใช้ extends เราสามารถโอเวอร์ไรด์ หรือเขียนทับฟังก์ชันใด ๆ ได้ (ไม่ว่าจะประกาศเป็น protected หรือเป็น public ) การเขียนทับ เมื่อเรารู้สึกว่า การทำงานของฟังก์ชันจากคลาสฐานไม่ตรงตามความต้องการ หรือไม่อยากให้ทำงานเหมือนคลาสฐาน
Code 21. Raido.php<?php
class Radio extends Product{
public function info(){
echo "Radio:" . $this->getId() . ":" . $this->getName();
}
}
ทดสอบเรียกใช้ จะเห็นว่าทำงานไม่เหมือนคลาสฐาน (Product) แต่จะทำงานตามแนวทางที่เขียนขึ้นมาใหม่ ลักษณะเช่นนี้ เป็นปรากฏการที่เรียกว่า โพลิมอร์ฟิสึม (Polymorphism)
Code 22. index.php<?php
include 'Product.php';
include 'Radio.php';
$radio = new Radio("Tarnin");
$radio->info();
//Output:Radio:1:Tarnin
ป้องกันการเขียนทับได้ด้วย 'final'
ถ้าเราไม่ต้องการเปลี่ยนแปลงฟังก์ชันในคลาสที่สืบทอด ได้อีก เราสามารถป้องกันได้ ด้วยคำสำคัญว่า final เช่น ใน info( ) ให้สิ้นสุดที่คลาส Radio จะเขียนได้ว่า
Code 23. Radio.php<?php
class Radio extends Product{
public final function info(){
echo "Radio:" . $this->getId() . ":" . $this->getName();
}
}
คลาสคลุมเครือ (Abstract class)
ที่เขียนว่าคลุมเครือ หรือไม่ระบุให้ชัดไปเลยว่าให้ทำงานได้อย่างไร เราเรียกคลาสประเภทนี้ว่าแอบสแตรกท์ (Abstract) แล้วทำไมต้องเขียนคลาสประเภทนี้ไว้ด้วย โดยทั่วไปเมื่อเราตั้งใจจะทำให้คลาสไม่มีความชัดเจนในการทำงาน ก็เพื่อให้ผู้ที่นำคลาสไปใช้ต้องสร้างความชัดเจนเองภายหลัง ด้วยเหตุผลว่า ต้องการใช้ผู้ใช้ปรับแต่งได้เองตามที่ตนเองต้องการ เช่น คลาส Product หากเราไม่ต้องการระบุให้ชัดว่า ทำรายงานในหน้าเว็บแบบใด เราก็เขียนกำกับคลาส และฟังก์ชัน ด้วยคำสำคัญ abstract ดังเขียนคลาส Product และหน้าฟังก์ชัน report( ) ได้ใหม่ว่า
Code 24. Product.php (ต่อ)<?phpd
abstract class Product {
//เขียนเติมจากคลาสเดิมว่า
abstract function report();
}
ให้สังเกตว่า ฟังก์ชันที่คลุมเครือนี้ เป็นฟังก์ชันที่ไม่มีปีก มีเพียงวงเล็บต่อท้ายเท่านั้น เมื่อไม่มีปีก ก็ไม่รู้ว่าฟังก์ชันนี้ใช้ทำอะไรได้บ้าง และเราเรียกใช้ ผ่านคลาสที่สืบทอด ได้ว่า
Code 25. Radio.php2?php
class Radio extends Product{
public function report(){
echo "Radio:" . $this->getId() . ":" . $this->getName() . "<br>";
}
}
การสร้างคลาสให้มีฟังก์ชันเป็น abstract เป็นเหมือนประหนึ่งว่า ต้องการสร้างชื่อฟังก์ชันให้มาตรฐานเดียวกัน เมื่อคลาสสืบทอดนำไปใช้ ก็ต้องใช้ชื่อฟังก์ชันนี้เหมือนกัน เพียงแต่แสดงพฤติกรรมต่างกัน ตามการเขียนขยายการใช้งานฟังก์ชันนี้
คลาสไม่มีชื่อ
ถ้าเราต้องการสร้างออบเจ็กต์ แบบปัจจุบันทันด่วน (โดยตั้งชื่อไม่ทัน) เพื่อใช้เฉพาะกิจ หรือชื่อนั้นไม่ได้สำคัญอะไรไปมากกว่าการทำงาน การจะสร้างเป็นชื่อ เป็นเรื่องเป็นราว โดยไม่ได้ตั้งใจจะนำไปใช้สร้างในครั้งต่อไป คลาสแบบนี้เราเรียกกันตรง ๆ ว่า คลาสไม่มีชื่อ หรือ อโนนิมัสคลาส (Anonymous class)
สมมุติว่าเราต้องการสร้าง ที่เก็บรายการสินค้า และรายการร้องขอสินค้า ซึ่งเก็บคู่ ๆ เช่น ออบเจ็กต์ tv มีความต้องการ 2 หน่วย ออบเจ็กต์ radio ต้องการ 4 หน่วย เหตุการณ์แบบนี้ เราสร้างเป็นคลาสเพื่อเก็บรายการสินค้าดังกล่าว ได้ดัง ต่อไปนี้
Code 26. index.php<?php
include "Product.php";
$tv = new Product();
$tv->setId(1)->setName("TV")->setStock(100)->setPrice(1000);
$radio = new Product();
$radio->setId(2)->setName("Radio")->setStock(1000)->setPrice(200);
//anonymous class
$products[ ] = (object)array("product" = > $tv, "qty" => 2);
$products[ ] = (object)array("product" => $radio, "qty" => 4);
การเรียกอ่าน ก็เหมือนกับการอ่านสมาชิกทั่วไปของคลาส ที่ยกตัวอย่าง มีสมาชิก product และ qty เรียกอ่านได้
Code 27. index.php<?php
echo $products[0]->product->getName( ); //print TV
echo $products[0]->qty; //print 2
สำหรับ PHP7 สามารถ มีความสมบูรณ์มากขึ้นในด้านการสนับสนุนการเขียนโปรแกรมเชิงวัตถุ แนวทาการสร้างคลาสไม่มีชื่อ สามารถเขียนแนวทางใหม่ ได้ดังจากตัวอย่างต่อไปนี้
Code 28. index.php<?php
$object = new class {
public function hello($message) {
return "Hello $message";
}
};
echo $object->hello('PHP7');//print "Hello PHP7";
เห็นแล้ว การสร้างคลาสไม่ชื่อแบบใหม่ ดูชั้นสูงขึ้น
อินเทอร์เฟส (Interface)
อินเทอร์เฟสก็คือคลาสว่างเปล่า ซึ่งภายในมีได้เพียงการประกาศฟังก์ชัน คลาสใดที่ใช้งานอินเทอร์เฟสจะต้องเขียนเพิ่มเติมฟังก์ชันเอง ดังนั้นอินเทอร์เฟสไม่มีอะไรนอกจากกฎที่วางไว้ ซึ่งจะช่วยให้สืบทอดไปยังคลาสใด ๆ และจะต้องเขียนเติมทุกฟังก์ชันของอินเทอร์เฟส คลาสหนึ่งสามารถใช้อินเทอร์เฟสใด ๆ ด้วยการใช้คีย์เวิร์ด implements โปรดสังเกตว่าอินเทอร์เฟสสามารถได้เพียงประกาศชื่อฟังก์ชัน แต่ไม่สามารถเขียนคำสั่งใดในฟังก์ชันได้ นั้นหมายความว่าภายในตัวฟังก์ชันใด ๆ ต้องว่างเปล่า
แล้วทำไมจึงต้องมีอินเทอร์เฟสด้วย คำถามนี้ก็คลาสกับ คลาสที่เขียนคลุมเครือ แต่สิ่งที่ต่างออกไปมากกว่านั้น ก็คือว่า อินเทอร์เฟส มีฟังก์ชันให้ทำงานได้ทุกคลาส โดยที่ไม่จำเป็นต้องเกี่ยวเนื่องกับคลาสเลย เช่น เราต้องการสร้างอินเทอร์เฟสชื่อว่า DBDriver เพื่อใช้สำหรับการต่อเชื่อมกับฐานข้อมูล ให้กับคลาสต่าง ๆ ที่ต้องการใช้งานกับฐานข้อมูล ซึ่งฐานข้อมูลอาจเป็นต่างชนิด เช่น อาจเป็น MySQL หรือเป็น SQLite แล้วแต่ว่าระบบที่ใช้งานต้องการใช้งานฐานข้อมูลประเภทใด
ตอนนี้ถ้าจะให้แต่ละคนเขียนคลาส ในรูปแบบของเขาเอง เพื่อติดต่อกับฐานข้อมูล เขาอาจใช้ฟังก์ชัน ตามที่เขาชอบ และแน่นอนว่าแต่ละคนก็กำหนดชื่อได้ยากที่ตรงกันหมด คนที่ต้องการใช้คลาสก็ต้องรู้จักชื่อฟังก์ชัน จึงจะใช้งานคลาสได้ ยิ่งมีคลาสจำนวนมาก ที่ทำงานกับฐานข้อมูล ก็ต้องคอยจำว่าชื่อฟังก์ชันให้เป็นอะไร ดูแล้วไม่เป็นระเบียบ ซี่งทำให้ยุ่งยากในการดูแลรักษา แต่จะเป็นการดีที่จะมีอินเทอร์เฟส ที่กำหนดชื่อฟังก์ชันเป็นมาตรฐาน ใครต้องการใช้งาน ติดต่อกับฐานข้อมูล ก็ต้องใช้ชื่อฟังก์ชันเดียวกัน ที่มาจากอินเทอร์เฟสเดียวกัน ดังนั้นแล้ว ลองมาสร้างอินเทอร์เฟสนี้กัน
Code 29. DBDriver.php<?php
interface DBDriver{
public function connect();
public function execute($sql);
}
Code 28. index.php
สังเกตได้ไหมว่า เป็นฟังก์ชันว่างเปล่าในอินเทอร์เฟส ไม่มีปีกกา ตามหลัง หรือฟังก์ชันที่นิยามในคลาส อินเทอร์เฟสนี้ต่อไปนำมาใช้ในคลาส MySQLDriver ซึ่งต้องแต่งเติมทุก ๆ ฟังก์ชัน จากอินเทอร์เฟสนี้ และการนำไปชื่อต่อ จะต้องใส่คีย์เวิร์ด implements แทนที่จะเป็น extends
Code 30.MysqlDriver.php<?php
include("DBDriver.php");
class MySQLDriver implements DBDriver {
}
ตอนนี้ถ้าเราให้โปรแกรมนี้ทำงาน จะทำให้เกิดความผิดพลาดเพราะคลาส MySQLDriver ไม่ฟังก์ชัน connect ( ) และ execute ( ) ตามที่ได้นิยามไว้ในอินเทอร์เฟส ลองให้โปรแกรมทำงานแล้วจะได้พบข้อความแสดงความผิดพลาด
Fatal error: Class MySQLDriver contains 2 abstract methods
and must therefore be declared abstract or implement the remaining
methods (DBDriver::connect, DBDriver::execute)
เพื่อแก้ความผิดพลาดนี้ เราจำเป็นต้องเพิ่มสองฟังก์ชันของอินเทอร์เฟส ในคลาส MySQLDriver ของเรา มาดูโปรแกรมต่อไปนี้
Code 31. MySQLDriver.php<?php
include("DBDriver.php");
class MySQLDriver implements DBDriver {
public function connect() {
//connect to database
}
public function execute() {
//execute the query and output result
}
}
ถ้าเราให้โปรแกรมทำงาน เราจะได้ข้อความผิดพลาดต่อไปนี้อีกครั้ง
Fatal error: Declaration of MySQLDriver::execute() must be
compatible with that of DBDriver::execute() in
ข้อความที่ผิดพลาดมีแสดงว่าฟังก์ชัน execute ( ) ของเราไม่เข้ากันกับโครงสร้างฟังก์ชัน execute ( ) ที่ได้นิยามไว้ในอินเทอร์เฟส ถ้าตอนนี้เราดูในอินเทอร์เฟส เราจะพบว่า ฟังก์ชัน execute ( ) ควรมีหนึ่งตัวแปรเข้า ดังนั้นหมายความว่า เราจะอิมพลีเม้นท์ (Implement) หรือเขียนให้สมบูรณ์ ในคลาสนี้ ทุกโครงสร้างฟังก์ชันต้องเหมือนกับที่ได้นิยามไว้ในอินเทอร์เฟส ทีนี้ลองมาแก้คลาส MySQLDriver ใหม่ได้ดังต่อไปนี้
Code 32. MySQLDriver.php<?php
include("DBDriver.php");
class MySQLDriver implements DBDriver {
public function connect() {
//connect to database
}
public function execute($query) {
//execute the query and output result
}
}
เมื่อให้โปรแกรมทำงานก็จะไม่พบความผิดพลาดแล้ว แต่ฟังก์ชันเหล่านี้ ก็ไม่ได้เขียนให้ทำงานอะไร นอกจากจะใส่เพียงปีกกาคู่ให้ครบไวยากรณ์ของฟังก์ชันเท่านั้น
โพลิมอร์ฟิสึม (Polymorphism)
คุณสมบัติที่สำคัญอย่างหนึ่งที่เราเขียนโปรแกรมเชิงวัตถุคือ คุณสมบัติโพลิมอร์ฟิสึม ซึ่งก็คือ การปรับแปลงของวัตถุให้เหมาะกับการใช้งานได้เองของคลาสฐาน เช่น เราสร้างคลาส Product เป็นคลาสฐานของคลาส Radio ซึ่งเป็นไปได้ว่า เราอาจสร้างคลาสลูกของคลาส Product ได้อีกมากมาย และเมื่อเราต้องการเรียกใช้ฟังก์ชันของคลาสลูก เช่น function info( ) แต่เรียกผ่านคลาสฐาน การทำงานจะทำงานตามฟังก์ชันของคลาสลูกแทนที่จะเป็นคลาสฐาน แล้วทำไมต้องทำอย่างนี้ ลองนึกถึง การเก็บคลาสลูก ในลักษณะอาร์เรย์ โดยคลาสลูกคนละชนิดกัน แต่สร้างมาจากคลาสฐานเดียวกัน เราจะสร้างที่เก็บในลักษณะอาร์เรย์มีชนิดเป็นคลาสฐาน แทนที่จะมีชนิดเป็นคลาสลูก ซึ่งเห็นว่าสะดวกดี มาดูตัวอย่างกัน
Code 33. Computer.php<?php
class Computer extends Product{
public function report() {
echo "Computer:" . $this->getId() . ":" . $this->getName() . "<br>";
}
}
Code 34. Radio.php
<?php
class Radio extends Product{
public function report() {
echo "Radio:" . $this->getId() . ":" . $this->getName() . "<br>";
}
}
สองคลาสนี้ (Computer และ Radio) เป็นคลาสที่สืบทอดคุณสมบัติมาจากคลาสฐานเดียวกัน (Product) แต่มีเขียนฟังก์ชันไม่เหมือนกัน
ต่อมาทดสอบสร้างอาร์เรย์ มีแทนชนิดหรือ ไทป์ (Type) เป็น Product (ในภาษา PHP ไม่จำเป็นต้องระบุชนิดข้อมูล) และสร้างวออบเจ็กต์ 3 ตัว ออบเจ็กต์แรกมาจากคลาส Radio และที่เหลือมาจากคลาส Computer นำมาเก็บในอาร์เรย์นี้
Code 35. index.php<?php
include 'Product.php';
include 'Radio.php';
include 'Computer.php';
$products = array();
$products[ ] = new Radio("Tanin");
$products[ ] = new Computer("Acer");
$products[ ] = new Computer("IBM");
foreach($products as $p){
$p->report();
}
/* Output:
Radio:1:Tanin
Computer:2:Acer
Computer:3:IBM
*/
จากตัวอย่างนี้จะเห็นว่า ใช้อาร์เรย์เก็บออบเจ็กต์ทั้งหมดไว้ด้วยกัน และใช้คำสั่งวนซ้ำด้วย foreach เพื่ออ่านโดยใช้ตัวแปรเดียวกันได้ โดยไม่ต้องระบุเลยว่า ต้องเป็นออบเจ็กต์จากคลาสลูกตัวตัวใด ให้ผลการทำงานตามฟังก์ชัน ที่ได้ทำโอเวอร์ไรด์ ของแต่ละออบเจ็กต์ของคลาสลูก
เนมสเปส (Namespace)
การจัดไฟล์ให้อยู่เป็นห้อง จัดกลุ่มไฟล์พวกเดียวกันไว้ให้ที่เดียวกัน หรือเรียกว่า อยู่เป็นห้อง ๆ ในทางคอมพิวเตอร์ใช้เก็บไฟล์อยู่ในไดเรกทอรี ดังนั้นไฟล์ที่อยู่คนละห้องกัน แต่ไฟล์ชื่อเดียวกัน ก็สามารถทำได้ ที่มีชื่อไฟล์เดียวกัน และชื่อคลาสเดียวกัน แต่อยู่ต่างไดเรกทอรี ทำงานรวมกันได้ด้วยการใช้ เนมสเปส
ตัวอย่างต่อไปนี้ กำหนดให้ มีคลาส Radio ที่มาจากเนมสเปส tool และคลาสชื่อเดียวกันนี้ ไม่ได้มาจากเนมสเปสใด การเรียกใช้สองคลาสนี้ เพียงแต่ระบุเนมสเปสให้ถูกต้อง
Code 36. Radio.php<?php
namespace tools;
class Radio {
public function report(){
echo "My name is a special radio." ;
}
}
Code 37. index.php
<?php
include 'Radio.php';
include 'tools/Radio.php';
$products = array();
$products[] = new Radio("Tanin");
$products[] = new tools\Radio();
foreach($products as $p){
$p->report();
}
แต่สิ่งน่าสังเกตอย่างหนึ่งคือเราจะต้องระบุไฟล์ร่วมใช้งานอยู่ด้วย และต้องระบุเส้นทางของไฟล์ที่ต้องการใช้งาน ก่อนการเรียกใช้ เพียงแต่ใช้ชื่อคลาสเดียวกัน และการใช้เนมสเปสนี้ใช้ได้กับ PHP ตั้งแต่รุ่น 5.3.0 ขึ้นไป กรณีที่สร้างเนมสเปสย่อย ๆ เช่น ต้องการสร้าง products เป็น เนมสเปส ย่อยของ tools ก็เขียนได้ว่า tools\products ซึ่งใช้ \ เป็นตัวคั่น
ในบางครั้ง คลาสบางคลาส จำเป็นต้อง เรียกใช้งานในคลาส ที่อยู่ในเนมสเปสที่กำหนดขึ้นเอง เช่น คลาส PDO ถูกเขียนในคลาส MyDb ซึ่งคลาส MyDb มีเนมสเปสของตนเอง ดังนั้นเมื่อเรียกใช้คลาส PDO ในคลาส MyDb จึงให้เนมสเปสที่ต่างกัน โดยเฉพาะเมื่อใช้กับโหลลไฟล์อัตโนมัติ จึงจำเป็นต้องกำหนด เนมสเปส ของ PDO แยกจากกัน โดยใช้คีย์เวิร์ด use ก่อนประกาศคลาส ดังเขียนได้ว่า:
use \PDO;
Autoload
ที่ผ่านมา การใช้ เนมสเปส มองแล้วไม่ค่อยสะดวก เมื่อต้องสร้างการ include ทุกครั้งที่ใช้งาน ใน PHP มีฟังก์ชันการโหลดไฟล์อัตโนมัติ ที่ชื่อ __autoload($class) เป็นฟังก์ชันประเภทมายากลตัวหนึ่ง (Magic function) จากตัวอย่างที่ผ่านมา ให้ทำการโหลดคลาสอัตโนมัติ ผ่านตัวแปรเข้าที่เป็นชื่อคลาส ให้สังเกตว่า ตัวแปรเป็นเพียงชื่อคลาส การโหลดไฟล์ ต้องโหลดทั้งชื่อไฟล์และนามสกุลไฟล์ จึงต้องต่อกับ นามสกุลไฟล์ให้ด้วย
Code 38. index.php<?php
function __autoload($class){
include $class.'.php';
}
use tools\Radio as R;
$products = array();
$products[] = new Radio();
$products[] = new R();
foreach($products as $p){
$p->report();
}
แต่ฟังก์ชันมายากลนี้จะหมดอายุการใช้งานใน PHP รุ่นหลัง ๆ แล้ว โดยเฉพาะ PHP 7 ขึ้นไป ซึ่งจะมีฟังก์ชันอีกตัวชื่อ spl_autoload_register( $function ) ใช้แทน __autoload($class) ได้ ลองดูจากตัวอย่างต่อไปนี้ ที่ทำให้ได้เหมือนกับตัวอย่างก่อนหน้านี้
Code 39. index.php<?php
spl_autoload_register(function($class){
include $class.'.php';
});
use tools\Radio as R;
$products = array();
$products[] = new Radio();
$products[] = new R();
foreach($products as $p){
$p->report();
}
กรณีที่ เขียนการโหลดไฟล์อัตโนมัติ ซึ่งเขียนใน ไดเร็กทรอรีย่อย ก็ต้องออกมาที่ไดเร็กทรอรีปกติก่อน และคลาสที่ใช้กับเนมสเปส ก็จะใช้งานได้ตามปกติ เช่น เมื่ออยู่ในหนึ่งไดเร็กทรอย่อยชั้นเดียว จะเขียนใหม่คือ
spl_autoload_register(function($class){
include '../'.$class.'.php';
});
การใช้ฟังก์ชัน spl_autoload_register($funciton) ใช้รูปแบบฟังก์ชัน ซึ่งเป็นรูปแบบการเขียนโปรแกรมเชิงฟังก์ชัน ซึ่งจะได้พบลักษณะการเขียนโปรแกรมแบบนี้อีกครั้ง ในบทที่ว่าด้วยการเขียนโปรแกรมเชิงฟังก์ชัน
แบบฝึกัหด
ถ้ากำหนดให้ คลาส Product มีข้อมูลดังต่อไปนี้
<?php
//Product.php
class Product{
private $id;
private $name;
public function info(){
return $this->id . ":" . $this->name;
}
}
- สร้างฟังก์ชัน setId และ setName เพื่อกำหนด ค่า id และ name
- ทดสอบ สร้างวัตถุ p1 จากคลาส Product และเรียกใช้ฟังก์ชัน info
- แก้ไขฟังก์ชัน setId และ setName ให้คืนค่า วัตถุตัวเอง
- ทดสอบ สร้างวัตถุ p2 จากคลาส Product ให้เรียก setName ผ่านฟังก์ชัน setId แล้วเรียกใช้ฟังก์ชัน info
- สร้างคลาส Computer ให้สืบทอดคลาส Product โดยกำหนดให้คลาส Computer มีคุณสมบัติเพิ่มเติม มีคุณสมบัติ price กำหนดเป็น private และ getter และ setter ตามลักษณะที่ทำมาก่อนหน้านี้
- ทดสอบ สร้างวัตถุ c1 จากคลาส Computer กำหนดค่า id, name, price และเรียกใช้ฟังก์ชัน info
- ทำการ override ฟังก์ชัน info ให้เพิ่มการคืนค่า price
- ทดสอบสร้างวัตถุ c2 จากคลาส Computer กำหนดค่า id, name, price แล้วเรียกฟังก์ชัน info
- สร้างคลาส Radio ให้สืบทอดมาจากคลาส Product กำหนดคุณสมบัติราคา เหมือนกับคลาส Computer
- ทำการ โอเวอร์ไรด์ ฟังก์ชัน info ให้เพิ่มการคืนค่าราคา
- ทดสอบสร้างวัตถุ r1 จากคลาส Radio กำหนดค่า id, name, price แล้วเรียกฟังก์ชัน info
- สร้าง อาร์เรย์ $products เก็บ ค่า c1, c2, r1 เก็บไว้ในที่เดียวกัน
- วนซ้ำเรียก ฟังก์ชัน info