PHP OO 物件導向基礎教學



PHP OO 基礎教學

此篇教學只是物件導向的基礎與實作,內容只包含類別與物件的操作,讓不熟悉類別的人可以初識物件導向的好處,並且了解物件與類別的特性與關係。

認識物件導向 Object Oriented:

物件導向是一種寫程式的方式,它傾向讓開發者把類似或有關聯的工作或屬性,組織到類別(classes)裡面。這可以讓程式保持遵守 不重複原則**“don’t repeat yourself” (DRY) **,且更容易維護。
“Object-oriented programming is a style of coding that allows developers to group similar tasks into classes.”
不重複的程式(DRY)是物件導向最主要的優點之一,物件導向嚇跑了很多的開發者,因為它帶引入了一些新的語法,並且一看就知道比直譯程式(procedural)還複雜。但是,其實仔細了解一下,OO 實際上是一種非常直觀且是簡化程式最好的方法。

一、認識物件與類別 Understanding Objects and Classes

在開始更深入了解 OOP 之前,一定要先了解 物件(object)類別(class) 的差異。本章節會介紹類別(class)的構造,還有其功能與用途。
  • 類別(class),可以比喻為一間房子的藍圖,類別只是清楚得定義房子的結構與形狀。
  • 物件(object),可以比喻為一棟真的房子,物件是類別的實例化
資料(data)就像是鋼筋、電線、混泥土等蓋房子的材料。如果沒有按照 藍圖(類別 class) 來組裝,這些只會是一堆堆的材料。但是當材料按照 藍圖(類別 class) 的定義來打造房子時,這些材料最終變成了一個有組織化且有用的房子(物件 object)
類別定義了結構以及行為,並且用這些東西打造物件。當多個物件都是由同一個類別產生出來時,每個物件都是一個獨立的個體,且不相依賴的。
回到藍圖與房子的舉例,現在有 150 間房子都是由同一張藍圖所打造出來的。雖然150間房子看起來是一樣的,但是每一間都是獨立的,而且裡面住著不一樣的人和家具擺設。

二、建立類別 Class

建立類別的語法很簡單:使用 class 關鍵字來定義一個類別,然後在類別名稱後面再加上一對大括號{}
<?php
class MyClass
{
    // 類別的屬性與方法要在大括號裡面宣告。
    // Class properties and methods go here
}

$obj = new MyClass;
var_dump($obj);
在建立類別之後,可以使用 new 關鍵字來實例化一個類別,並且將它存到一個變數上:
$obj = new MyClass;
使用 var_dump()來查看這個類別的內容:
var_dump($obj);
現在,建立一個新的檔案 test.php並且將這些程式放進去測試一下吧:
test.php:
<?php
class MyClass
{
    // 類別的屬性與方法要在大括號裡面宣告。
    // Class properties and methods go here
}

$obj = new MyClass;
var_dump($obj);
上面的程式的應該會輸出:
object(MyClass)#1 (0) { }
由輸出結果顯示,這就是一個最簡單的物件形式,你已經完成了你的第一個物件導向程式了。

三、定義類別的屬性 Defining Class Properties

使用屬性(Property),也稱作 類別的變數(Variable)來把資料(Data)存入一個類別裡面。存取的用法就像普通變數一樣,除非這些變數被物件給綁定了,被綁定的變數只能由物件本身存取。
將下列程式碼增加到你的程式中,替 MyClass 加入一個屬性(Property):

public 關鍵字用來決定屬性(Property)的可視性(Visibility),這將是你在本章節之後會學習的觀念。接下來,定義 屬性名稱 與賦予一個值給屬性(註:類別的屬性不初始值)。
指定要讀取的 物件 以及 屬性,並將它顯示在瀏覽器上:
echo $obj->prop1;
因為有很多物件實例化自同一個類別,如果沒有指定這些被實例化的物件的話,程式碼將沒辦法判斷應該一讀取哪一個物件。
箭頭->在 PHP 的物件中,用來存取物件的屬性(Property)和方法(Methods)。
現在,修改 test.php 來讀取屬性,而不再使用var_dump來倒出整個物件的內容:

重新整理一下你的瀏覽器,應該會得到下列結果:
I'm a class property!

四、定義類別的方法 Defining Class Methods

方法(Methods)是類別裡面的函式(Functions),物件可以藉由執行這些方法來更動每個物件的行為或狀態。
舉個例子,建立一個方法來設定與讀取屬性$prop1的值:
<?php
class MyClass
{
    public $prop1 = "I'm a class property!";

    public function setProperty($newval)
    {
        $this->prop1 = $newval;
    }

    public function getProperty()
    {
        return $this->prop1 . "<br />";
    }
}

$obj = new MyClass;
echo $obj->prop1;
記下來
物件導向允許物件透過 $this 關鍵字來參考自己。物件使用 $this 就如同你直接使用物件名稱來指定物件,如: myClass->prop1
使用這些含有 $this 的方法之前,記得先要實例化這些方法的物件。
範例:
讀取MyClass類別的屬性,更改屬性的值,最後再把屬性輸出到瀏覽器上:
<?php
class MyClass
{
    public $prop1 = "I'm a class property!";

    public function setProperty($newval)
    {
        $this->prop1 = $newval;
    }

    public function getProperty()
    {
        return $this->prop1 . "<br />";
    }
}

$obj = new MyClass;
echo $obj->getProperty(); // Get the property value

$obj->setProperty("I'm a new property value!"); // Set a new one
echo $obj->getProperty(); // Read it out again to show the change
重新整理你的瀏覽器,你將會看到以下結果:
I'm a class property!
I'm a new property value!
當同一個類別被實例化越多次的時候,OOP 的力量也會隨著變得更強大。
The power of OOP becomes apparent when using multiple instances of the same class.
接下來我們來實例化第二個類別:
<?php
class MyClass
{
    public $prop1 = "I'm a class property!";

    public function setProperty($newval)
    {
        $this->prop1 = $newval;
    }

    public function getProperty()
    {
        return $this->prop1 . "<br />";
    }
}

// Create two objects
$obj = new MyClass;
$obj2 = new MyClass;

// Get the value of $prop1 from both objects
echo $obj->getProperty();
echo $obj2->getProperty();

// Set new values for both objects
$obj->setProperty("I'm a new property value!");
$obj2->setProperty("I belong to the second instance!");

// Output both objects $prop1 value
echo $obj->getProperty();
echo $obj2->getProperty();
重新整理你的瀏覽器,你應該會看見以下結果:
I'm a class property!
I'm a class property!
I'm a new property value!
I belong to the second instance!
如你所見,物件導向將 物件 視為獨立的個體,就如同 150 間房子都是獨立的實體一樣。這個特性把程式碼切割成一個個又小又有組織的個體。換句話說,這個特性讓原本需要寫 150 次才能完成的程式碼,切割成 150 個又小又有組織的物件。達到了程式碼不重複原則”Don’t Repeat yourself”(DRY)。

封裝 Encapsulation

封裝是以資料為核心,將相關的資料放在一起,把會用到這些資料的方法也放進來。為了和非OO的領域做區隔,OO做了名詞上的改變如:將函式(Function)改為 方法(Method)、將呼叫 (Call)改為調用(Invoke)。
一般會以類別來封裝有相關的行為與資料。

五、魔術函數 Magic Methods in OOP

PHP 提供了幾個 魔術函數 操 作物件(object) 變得更簡單,這些魔術函數會在物件發生特定行為時被呼叫。這讓開發者更容易達成某些有用的任務。
更多魔術函數可以看: PHP 官方網站的魔術函數文件

使用 Constructor

當一個物件被建立時,它經常需要做一些初始化。PHP 提供了一個 __construct()的魔術函數,當一個物件被建立的時候會被呼叫。
為了說明 __construct() 的觀念,增加一個__construct()MyClass 類別,讓 MyClass 類別被實例化的時候,丟出一段訊息:
<?php
class MyClass
{
    public $prop1 = "I'm a class property!";

    public function __construct()
    {
        echo 'The class "', __CLASS__, '" was initiated!<br />';
    }

    public function setProperty($newval)
    {
        $this->prop1 = $newval;
    }

    public function getProperty()
    {
        return $this->prop1 . "<br />";
    }
}

// Create a new object
$obj = new MyClass;

// Get the value of $prop1
echo $obj->getProperty();

// Output a message at the end of the file
echo "End of file.<br />";
記下來
CLASS 叫做魔術常數 magic constant.會回傳被呼叫的類別的名稱。
更多魔術常數可以看: PHP 官方網站的魔術常數文件
重新整理瀏覽器,應該會產生以下結果:
The class "MyClass" was initiated!
I'm a class property!
End of file.

使用 Destructors

在物件被摧毀的時候呼叫__destruct() 魔術函數,這對於清除一個物件來說很有用(例如:關閉資料庫連線等。)
增加一個__destruct()MyClass 類別,讓 MyClass 類別被摧毀時的時候,丟出一段訊息:
<?php
class MyClass
    {
    public $prop1 = "I'm a class property!";

    public function __construct()
    {
        echo 'The class "', __CLASS__, '" was initiated!<br />';
    }

    public function __destruct()
    {
        echo 'The class "', __CLASS__, '" was destroyed.<br />';
    }

    public function setProperty($newval)
    {
        $this->prop1 = $newval;
    }

    public function getProperty()
    {
        return $this->prop1 . "<br />";
    }
}

// Create a new object
$obj = new MyClass;

// Get the value of $prop1
echo $obj->getProperty();

// Output a message at the end of the file
echo "End of file.<br />";
加入一個__destruct()之後,重新頁面:
The class "MyClass" was initiated!
I'm a class property!
End of file.
The class "MyClass" was destroyed.
當一個物件使用完畢時,PHP 會自動釋放其記憶體。
“When the end of a file is reached, PHP automatically releases all resources.”
為了更明確的觸發__destruct()魔術函數,可以透過unset()方法來摧毀物件:
<?php
class MyClass
{
    public $prop1 = "I'm a class property!";

    public function __construct()
    {
        echo 'The class "', __CLASS__, '" was initiated!<br />';
    }

    public function __destruct()
    {
        echo 'The class "', __CLASS__, '" was destroyed.<br />';
    }

    public function setProperty($newval)
    {
        $this->prop1 = $newval;
    }

    public function getProperty()
    {
        return $this->prop1 . "<br />";
    }
}

// Create a new object
$obj = new MyClass;

// Get the value of $prop1
echo $obj->getProperty();

// Destroy the object
unset($obj);

// Output a message at the end of the file
echo "End of file.<br />";
重新整理頁面後會顯示以下結果:
The class "MyClass" was initiated!
I'm a class property!
The class "MyClass" was destroyed.
End of file.

__toString 將物件轉換為字串

PHP 提供 __toString()魔術函數來避免程式試圖將 MyClass當作字串輸出到瀏覽器上。(被當作字串處理時觸發)
如果把物件當作字串處理的話,在 PHP 中會出現無法轉換型態的錯誤:
// Create a new object
$obj = new MyClass;

// Output the object as a string
echo $obj;
結果如下:
The class "MyClass" was initiated!
Catchable fatal error: Object of class MyClass could not be converted to string in /Applications/MAMP/htdocs/LearningPHP/test.php on line 40
為了避免發生轉換型態的錯誤,加入一個__toString()函數來做轉換處理:
<?php
class MyClass
{
    public $prop1 = "I'm a class property!";

    public function __construct()
    {
        echo 'The class "', __CLASS__, '" was initiated!<br />';
    }

    public function __destruct()
    {
        echo 'The class "', __CLASS__, '" was destroyed.<br />';
    }

    public function __toString()
    {
        echo "Using the toString method: ";
        return $this->getProperty();
    }

    public function setProperty($newval)
    {
        $this->prop1 = $newval;
    }

    public function getProperty()
    {
        return $this->prop1 . "<br />";
    }
}

// Create a new object
$obj = new MyClass;

// Output the object as a string
echo $obj;

// Destroy the object
unset($obj);

// Output a message at the end of the file
echo "End of file.<br />";
在這個情況下,嘗試將物件轉換成字串的時候,會觸發 __toString 函數,再由 __toString 函數呼叫 getProperty() 方法。重新整理瀏覽器來查看最新的結果:
The class "MyClass" was initiated!
Using the toString method: I'm a class property!
The class "MyClass" was destroyed.
End of file.
除了本章節提到的__construct__destruct__toString三個魔術函數以外,還有更多魔數函數可以在 PHP 手冊中可以學習唷!

六、類別繼承 Class Inheritance

使用 extend 關鍵字可以讓類別繼承其他類別的 方法(methods) 和 屬性(property)
 範例:建立一個新的類別並且繼承(extends)MyClass類別,新的類別可以使用 MyClass 原有的 方法(methods) 和 屬性(property)
<?php
class MyClass
{
    public $prop1 = "I'm a class property!";

    public function __construct()
    {
        echo 'The class "', __CLASS__, '" was initiated!<br />';
    }

    public function __destruct()
    {
        echo 'The class "', __CLASS__, '" was destroyed.<br />';
    }

    public function __toString()
    {
        echo "Using the toString method: ";
        return $this->getProperty();
    }

    public function setProperty($newval)
    {
        $this->prop1 = $newval;
    }

    public function getProperty()
    {
        return $this->prop1 . "<br />";
    }
}

class MyOtherClass extends MyClass
{
    public function newMethod()
    {
        echo "From a new method in " . __CLASS__ . ".<br />";
    }
}

// Create a new object
$newobj = new MyOtherClass;

// Output the object as a string
echo $newobj->newMethod();

// Use a method from the parent class
echo $newobj->getProperty(); 
網頁重新整理後,可以看見以下結果:
The class "MyClass" was initiated!
From a new method in MyOtherClass.
I'm a class property!
The class "MyClass" was destroyed.

覆寫(Override) 繼承的方法和屬性

你可以在新類別裡面重新定義來更改繼承的屬性和方法的行為
<?php
class MyClass
{
    public $prop1 = "I'm a class property!";

    public function __construct()
    {
        echo 'The class "', __CLASS__, '" was initiated!<br />';
    }

    public function __destruct()
    {
        echo 'The class "', __CLASS__, '" was destroyed.<br />';
    }

    public function __toString()
    {
        echo "Using the toString method: ";
        return $this->getProperty();
    }

    public function setProperty($newval)
    {
        $this->prop1 = $newval;
    }

    public function getProperty()
    {
        return $this->prop1 . "<br />";
    }
}

class MyOtherClass extends MyClass
{
    public function __construct()
    {
        echo "A new constructor in " . __CLASS__ . ".<br />";
    }

    public function newMethod()
    {
        echo "From a new method in " . __CLASS__ . ".<br />";
    }
}

// Create a new object
$newobj = new MyOtherClass;

// Output the object as a string
echo $newobj->newMethod();

// Use a method from the parent class
echo $newobj->getProperty();
於新類別中覆寫 __construct 方法的輸出結果變成:
A new constructor in MyOtherClass.
From a new method in MyOtherClass.
I'm a class property!
The class "MyClass" was destroyed.

保留父類別的方法 —-範圍解析運算子(scope resolution operator)

通常父類別同名方法的程式碼內容很多,不可能就幾行,如果我們想保留原有的功能,另外再擴展出一點點的功能,但是又不想把原有的程式碼再重寫一次,這樣就可以使用範圍解析運算子``::來呼叫父類別中被覆蓋的方法。

記下來

範圍解析運算子,在沒有宣告任何實體的情況下,存取類別中的函數、常數、靜態屬性。使用範圍解析運算子存取類別時,仍然受到public、protected、private可視性原則的限制。
新增改寫一個父類別的函數,並使用範圍解析運算子(scope resolution operator) :: 來調用父類別被覆寫的函數:
<?php
class MyClass
{
    public $prop1 = "I'm a class property!";

    public function __construct()
    {
        echo 'The class "', __CLASS__, '" was initiated!<br />';
    }

    public function __destruct()
    {
        echo 'The class "', __CLASS__, '" was destroyed.<br />';
    }

    public function __toString()
    {
        echo "Using the toString method: ";
        return $this->getProperty();
    }

    public function setProperty($newval)
    {
        $this->prop1 = $newval;
    }

    public function getProperty()
    {
        return $this->prop1 . "<br />";
    }
}

class MyOtherClass extends MyClass
{
    public function __construct()
    {
        parent::__construct(); // Call the parent class's constructor
        echo "A new constructor in " . __CLASS__ . ".<br />";
    }

    public function newMethod()
    {
        echo "From a new method in " . __CLASS__ . ".<br />";
    }
}

// Create a new object
$newobj = new MyOtherClass;

// Output the object as a string
echo $newobj->newMethod();

// Use a method from the parent class
echo $newobj->getProperty();
以下結果顯示,即使新類別覆寫了父類別的 contruct()方法,但仍可透過::來調用原父類別的 contruct()方法:
The class "MyClass" was initiated!
A new constructor in MyOtherClass.
From a new method in MyOtherClass.
I'm a class property!
The class "MyClass" was destroyed.

七、替屬性和方法加上可視性 Assigning the Visibility of Properties and Methods

加入可視性(Visibility),可以決定能不能從物件外面控制物件的方法和屬性。
可視性有三個關鍵字:publicprotectedprivate。除此之外,還有一個稍微不一樣的叫做 staticstatic 可以不用將類別實例化就可以調用。
替屬性和方法加上可視性,是為了增加對物件的控制。
For added control over objects, methods and properties are assigned visibility.

Public Properties and Methods

到目前為止,範例使用的方法和屬性的可視性都是 public。這意味著這些方法和屬性可以在類別的裡面與外面被存取。

Protected Properties and Methods

當一個變數或方法的可視性被宣告為 protected,該變數或方法只能在類別以及子類別的內部存取。
範例:把 MyClass 的 getProperty() 方法的可視性宣告為 protected,並且嘗試從外面呼叫這個方法:
<?php

class MyClass
{
    public $prop1 = "I'm a class property!";

    public function __construct()
    {
        echo 'The class "', __CLASS__, '" was initiated!<br />';
    }

    public function __destruct()
    {
        echo 'The class "', __CLASS__, '" was destroyed.<br />';
    }

    public function __toString()
    {
        echo "Using the toString method: ";
        return $this->getProperty();
    }

    public function setProperty($newval)
    {
        $this->prop1 = $newval;
    }

    protected function getProperty()
    {
        return $this->prop1 . "<br />";
    }
}

class MyOtherClass extends MyClass
{
    public function __construct()
    {
        parent::__construct();
    echo "A new constructor in " . __CLASS__ . ".<br />";
    }

    public function newMethod()
    {
        echo "From a new method in " . __CLASS__ . ".<br />";
    }
}

// Create a new object
$newobj = new MyOtherClass;

// try to call a protected method
echo $newobj->getProperty();
執行這段程式碼之後,會出現以下 Call to protected method 錯誤:
The class "MyClass" was initiated!
A new constructor in MyOtherClass.
Fatal error: Call to protected method MyClass::getProperty() from context '' in /Applications/MAMP/htdocs/LearningPHP/test.php on line 55
接下來,在子類別 MyOtherClass 中新增一個新的方法來調用 getProperty()方法:<?php
class MyClass
{
    public $prop1 = "I'm a class property!";

    public function __construct()
    {
        echo 'The class "', __CLASS__, '" was initiated!<br />';
    }

    public function __destruct()
    {
        echo 'The class "', __CLASS__, '" was destroyed.<br />';
    }

    public function __toString()
    {
        echo "Using the toString method: ";
        return $this->getProperty();
    }

    public function setProperty($newval)
    {
        $this->prop1 = $newval;
    }

    protected function getProperty()
    {
        return $this->prop1 . "<br />";
    }
}

class MyOtherClass extends MyClass
{
    public function __construct()
    {
        parent::__construct();
    echo "A new constructor in " . __CLASS__ . ".<br />";
    }

    public function newMethod()
    {
        echo "From a new method in " . __CLASS__ . ".<br />";
    }

    public function callProtected()
    {
        return $this->getProperty();
    }
}

// Create a new object
$newobj = new MyOtherClass;

// Call the protected method from within a public method
echo $newobj->callProtected();
由結果可見,子類別可以調用父類別可性度為 protected 的方法:
The class "MyClass" was initiated!
A new constructor in MyOtherClass.
I'm a class property!
The class "MyClass" was destroyed.

Private Properties and Methods

當一個變數或方法的可視性被宣告為 private,該變數或方法只能在定義它們的類別內。如果有一個新的類別繼承了宣告有 private 屬性或方法的類別,新的類別(所有的子類別)將沒有辦法使用這些被宣告為 private 的屬性或方法。
範例:
將 MyClass 的 getProperty() 方法的可視度宣告為 private,並且使用 MyOtherClass 的 callProtected() 方法來調用 getProperty()方法。
<?php
class MyClass
{
    public $prop1 = "I'm a class property!";

    public function __construct()
    {
        echo 'The class "', __CLASS__, '" was initiated!<br />';
    }

    public function __destruct()
    {
        echo 'The class "', __CLASS__, '" was destroyed.<br />';
    }

    public function __toString()
    {
        echo "Using the toString method: ";
        return $this->getProperty();
    }

    public function setProperty($newval)
    {
        $this->prop1 = $newval;
    }

    private function getProperty()
    {
        return $this->prop1 . "<br />";
    }
}

class MyOtherClass extends MyClass
{
    public function __construct()
    {
        parent::__construct();
        echo "A new constructor in " . __CLASS__ . ".<br />";
    }

    public function newMethod()
    {
        echo "From a new method in " . __CLASS__ . ".<br />";
    }

    public function callProtected()
    {
        return $this->getProperty();
    }
}

// Create a new object
$newobj = new MyOtherClass;

// Use a method from the parent class
echo $newobj->callProtected();
重新載入瀏覽器,輸出結果如下:
The class "MyClass" was initiated!
A new constructor in MyOtherClass.
Fatal error: Call to private method MyClass::getProperty() from context 'MyOtherClass' in /Applications/MAMP/htdocs/LearningPHP/test.php on line 49

Static Properties and Methods

當一個變數或方法的可視性被宣告為 static,該變數或方法可以在類別還沒有被實例化就被調用。你可以透過範圍解析運算子(scope resolution operator) :: 來調用這些 static 屬性或方法。
範例:
在 MyClass 加入一個 static 變數與方法: $count、plusOne()。
在類別的外面使用 do…while 迴圈增加 $count 的值:
<?php
class MyClass
{
    public $prop1 = "I'm a class property!";

    public static $count = 0;

    public function __construct()
    {
        echo 'The class "', __CLASS__, '" was initiated!<br />';
    }

    public function __destruct()
    {
        echo 'The class "', __CLASS__, '" was destroyed.<br />';
    }

    public function __toString()
    {
        echo "Using the toString method: ";
        return $this->getProperty();
    }

    public function setProperty($newval)
    {
        $this->prop1 = $newval;
    }

    private function getProperty()
    {
        return $this->prop1 . "<br />";
    }

    public static function plusOne()
    {
        return "The count is " . ++self::$count . ".<br />";
    }
}

class MyOtherClass extends MyClass
{
    public function __construct()
    {
        parent::__construct();
        echo "A new constructor in " . __CLASS__ . ".<br />";
    }

    public function newMethod()
    {
        echo "From a new method in " . __CLASS__ . ".<br />";
    }

    public function callProtected()
    {
        return $this->getProperty();
    }
}

do {
  // Call plusOne without instantiating MyClass
    echo MyClass::plusOne();
} while ( MyClass::$count < 10 );
記下來
當使用範圍解析運算子存取 static 屬性時,記得要在屬性名稱前面加上錢字號” $ “
輸出結果為:
The count is 1.
The count is 2.
The count is 3.
The count is 4.
The count is 5.
The count is 6.
The count is 7.
The count is 8.
The count is 9.
The count is 10.

八、比較物件導向與直譯程式的差別

這兩種撰寫方式間沒有對和錯。只是如果使用物件導向來撰寫程式,在開發大型專案的時候會比較容易管理程式。

理由一、易於實作

雖然一開始可能令人生畏,但 OOP 確實提供了更簡單的方式來處理資料。
“While it may be daunting at first, OOP actually provides an easier approach to dealing with data.”
因為物件可以將資料儲存在內部屬性內,因此物件內部的方法不必一直把屬性裝入參數內就可以運行了。
再來,多個屬於同一個類別的物件可以同時存在。這在處理大量數據組合的時候可以把程式變得更加簡化。
舉例來說:
想像一下如果你有兩個人的資訊需要被處理,每個人包含 name 名稱、occupations 職業和 age 年齡。
直譯程式會這樣寫:
<?php
function changeJob($person, $newjob)
{
  $person['job'] = $newjob; // Change the person's job
  return $person;
}

function happyBirthday($person)
{
  ++$person['age']; // Add 1 to the person's age
  return $person;
}

$person1 = array(
  'name' => 'Tom',
  'job' => 'Button-Pusher',
  'age' => 34
);

$person2 = array(
  'name' => 'John',
  'job' => 'Lever-Puller',
  'age' => 41
);

// Output the starting values for the people
echo "<pre>Person 1: ", print_r($person1, TRUE), "</pre>";
echo "<pre>Person 2: ", print_r($person2, TRUE), "</pre>";

// Tom got a promotion and had a birthday
$person1 = changeJob($person1, 'Box-Mover');
$person1 = happyBirthday($person1);

// John just had a birthday
$person2 = happyBirthday($person2);

// Output the new values for the people
echo "<pre>Person 1: ", print_r($person1, TRUE), "</pre>";
echo "<pre>Person 2: ", print_r($person2, TRUE), "</pre>";
執行結果如下:
Person 1: Array
(
  [name] => Tom
  [job] => Button-Pusher
  [age] => 34
)

Person 2: Array
(
  [name] => John
  [job] => Lever-Puller
  [age] => 41
)

Person 1: Array
(
  [name] => Tom
  [job] => Box-Mover
  [age] => 35
)

Person 2: Array
(
  [name] => John
  [job] => Lever-Puller
  [age] => 42
)
雖然這段程式碼不一定是壞的,但是仍然有幾項事情需要注意。
裝著個人資訊的 Array 必須被每一個 function 當作參數傳遞,處理完之後還必須返回(return)回來,這就可能會留下一些錯誤的餘地(人為因素忘了加入參數或忘記返回等)
接下來整理一下上面的範例,如果可以的話,寫程式的時候盡可能留下最少的事情給開發者:應該被當作 function 的參數來傳遞的只能是必須被處理的資訊。
以下是透過物件導向來簡化你的程式:
<?php

class Person
{
    private $_name;
    private $_job;
    private $_age;

    public function __construct($name, $job, $age)
    {
        $this->_name = $name;
        $this->_job = $job;
        $this->_age = $age;
    }

    public function changeJob($newjob)
    {
        $this->_job = $newjob;
    }

    public function happyBirthday()
    {
        ++$this->_age;
    }
}

// Create two new people
$person1 = new Person("Tom", "Button-Pusher", 34);
$person2 = new Person("John", "Lever Puller", 41);

// Output their starting point
echo "<pre>Person 1: ", print_r($person1, TRUE), "</pre>";
echo "<pre>Person 2: ", print_r($person2, TRUE), "</pre>";

// Give Tom a promotion and a birthday
$person1->changeJob("Box-Mover");
$person1->happyBirthday();

// John just gets a year older
$person2->happyBirthday();

// Output the ending values
echo "<pre>Person 1: ", print_r($person1, TRUE), "</pre>";
echo "<pre>Person 2: ", print_r($person2, TRUE), "</pre>";
輸出結果如下:
Person 1: Person Object
(
  [_name:private] => Tom
  [_job:private] => Button-Pusher
  [_age:private] => 34
)

Person 2: Person Object
(
  [_name:private] => John
  [_job:private] => Lever Puller
  [_age:private] => 41
)

Person 1: Person Object
(
  [_name:private] => Tom
  [_job:private] => Box-Mover
  [_age:private] => 35
)

Person 2: Person Object
(
  [_name:private] => John
  [_job:private] => Lever Puller
  [_age:private] => 42
)
雖然上面OOP版本的範例中,花了一有點多的步驟讓這個步驟是使用物件導向來運行,但在類別被妥善定義之後,新增與修改個人資訊都只是一小小的變動而已。
在設計方法時,個人資訊不可以被當作參數與執行結果在傳遞,只有絕對必要的訊息才會被每一個方法傳遞。
如果實施正確的話,物件導向會明顯的降低你的工作量。
“OOP will significantly reduce your workload if implemented properly.”
上面的範例可能看不太出來有沒有使用物件導向的差異,但是當你的專案越來越龐大的時候,物件導向可以大大的降低你的工作量。

記下來

不是每一件事情都需要被物件導向。一個簡單、範圍小的功能是不需要被包裝成一個類別的!撰寫功能時。你必須在物件導向與直譯程式之間好好做個選擇。

理由二、更好的組織結構 Better Organization

第二個物件導向的好處是,它很容易被包裝(packaged)與分類(cataloged),每一個類別可以被切割到單一的檔案( 一個 php 檔案只有一個 class 原則)。將每一個類別分散於一個檔案,也讓程式變得更容易攜帶和重複使用。
如果你開發了新的專案就不需要大量的複製與貼上了!
假設你開發的應用程式有 150 個類別,而且他們是透過位於你專案根目錄的控制器(controller)動態載入這些類別。這時如果你有一個統一的分類與命名規則將可以很容易來存取這些類別。
註:透過 PHP 的 __autoload 自動載入魔術函數,可以在程式需要使用到類別時才自動將類別的檔案載入。

理由三、更方便維護

由於物件導向的性質,會讓程式變得更緊湊更有關聯性。相較於一長串的義大利麵式的程式,在修改程式時物件導向會更簡單以及更快速發現問題點。
在直譯程式中:
如果一個特定的陣列新增了新的屬性,那麽程式內有使用到該陣列的方法,可能必須替調用的陣列加入一個新的屬性,才能正常運作。
作法:必須修改N個方法。
在物件導向中:
一個物件導向開發的程式,可以簡單的更變或新增屬性或方法,一切的修改都只會在類別裡面。
作法:只需要修改一個類別。
物件導向的不重複自己的原則(DRY)替開發程式帶來許多好處,它讓程式變得更容易維護了。

結語

物件導向如果運用得正確,它應該會讓你的程式變易讀、易維護、易攜帶,這會減少你很多花額外的時間。

留言

  1. 謝謝大大的熱心分享,獲益良多。

    回覆刪除
  2. 爬過這麼多文,您是第一個讓我留言的大大呀!!!

    受益良多~~~感謝大大

    回覆刪除
  3. 細心講解,文章收穫很多,感謝分享!

    回覆刪除
  4. 非常詳細,淺顯易讀,感謝大大~~!

    回覆刪除
  5. 認真看完後,覺得解釋得很清楚,感謝大大

    回覆刪除
  6. 用「蓋房子」來類比介紹,真的容易多了!
    深入淺出(了解得很深入,說出來的很簡單)
    前教育部長 吳京 :「小孩子沒有學不會的,只是他還沒有找到學習的方法!」
    您的介紹真是造福太多人了!
    感恩!

    回覆刪除

張貼留言

這個網誌中的熱門文章

Git Commit Message 這樣寫會更好,替專案引入規範與範例

Gitlab 合併請求 Merge Request 是什麼?