iT邦幫忙

19

PHP物件導向的第三課:建構式

昨天好好睡了一覺,今天的精神有比較好一些。
昨天頭痛了一整天......
原本今天的課程是要講建構式和繼承的,但我發現光是和建構式相關的東西就可以寫很多很多。
所以今天這一堂課,我們就專門講建構式好了。
先來一段程式碼:

//基本是建議使用mysqli或是pdo,不過目前市面書籍很多還是只介紹mysql指令集
mysql_connect('localhost','root','password');
mysql_select_db('mydb');
mysql_query("SET NAMES 'utf8'");
$sql = "SELECT * FROM `user` WHERE name = 'sam'";
$result = mysql_query($sql);
if($result != false)
    while($row = mysql_fetch_object($result)){
        echo $row->name.'<br/>';
    }
}

觀眾:ㄟㄟㄟ!你給我等等,你是在上物件導向,說要教建構式,你寫個mysql讀取的程式碼幹嘛?

這……這當然和我的主題有關係啦。
回到物件這邊:
各位都知道,線上遊戲的技能有分「主動技」和「被動技」。
觀眾:你離題了......

所謂的主動技能是玩家要自己去發動的技能,而所謂的被動技就是指會自動去處理的技能。
前面所介紹的物件導向的方法(method),都是必須外部去呼叫執行該物件的方法才會進行動作。
但有的時候會有一種狀況就是每一次執行該物件都「必須」先執行某個或是一連串的動作。
這其實相當麻煩。
像上面那一段mysql語法,可能一些人會想說那我用函式包起來,以候呼叫函式就好了。
於是就變成了這樣:

function db_connect($host,$username,$password,$dbs){
    mysql_connect($host,$username,$password);
    mysql_select_db($db);
    mysql_query("SET NAMES 'utf8'");
}
db_connect('localhost','root','password','mydb');

是的,大致上來說這樣看起來算是頗為完美。
也有人乾脆就整個參數寫在程式內直接叫db_connect();
可問題是你每次去執行sql資料庫時,若要像第一段那樣來上一段。
相對的很麻煩。
用上述的函式處理其實很ok,只不過在更詳細的物件操作上,有絕對比較好的簡單做法。

我們將上面的code轉換成物件。

class db{

    public $host = 'localhost';
    public $username = 'root';
    public $password = 'password';
    public $database = 'mydb';

    function __construct(){
        $this->sql_connect();
        $this->sql_database();
        $this->set_db_encode();
    }

    function sql_connect(){
        return @mysql_connect($this->host,$this->username,$this->password);
    }

    function sql_database(){
        return @mysql_select_db($this->database);
    }

    function set_db_encode(){
        return mysql_query("SET NAMES 'utf8'");
    }

}
$db = new db;

在這個code中,我們看到了一個很特別的方法名稱:__construct();
這個__construct()就是我今天的主體:建構式。

觀眾:你講了那麼大一串,現在才講到?不過......什麼是建構式?

這邊就如我先前說的,你想要使用sql功能,每一次都要做連線、選資料庫、編碼。
這樣每次寫實在很累。包成函式是不錯的方法,但封裝成物件有他接下來使用上的絕對優勢。
而所謂的建構式,其意義為:
『當物件生成的同時,強制預先去實作執行物件本身的功能。』
也就是說,當我$db = new db;時。
sql_connect(),sql_database()及set_db_encode()這三個方法會被強制先執行。
這邊我得提一些基本的編寫概念。
基本上建構式(亦稱建構子)並沒有規範你一定得怎麼寫裡面的程式碼。
你要在裡面echo、if、for、while、撈資料庫、做壞事、搞破壞.................
都跟你平常正常寫程式是一樣的。
但是,基於讓程式碼可維護。
也因此對於建構式就會有基本二個原則:
1.建構式僅用來做為設置預設屬性值的存在。
例:

class demo{

    function __construct($name){
        $this->name = $name;
    }
}
$demo = new demo('sam');

※先前未交建構式前,各位會發現我new物件時並不會在物件的名稱加上括號:
$demo = new demo;
但是如果你的建構式中是必須給予參數的情形下,你就必須加上括號:
$demo = new demo('sam');
如果你本身對於這方面加與不加不是很肯定,其實我建議你可以全都加括號:
$demo = new demo();

2.建構式僅用來做為預載函式功能執行的存在:

class demo

    function __construct(){
        $this->Action();
    }

    function Action(){

    }
}

除了這二件事情外,僅可能的不要拿建構式去做其他多餘的事。

※在某些framework,因為建構式的內容應該要繼承於其父類別的建構式,通常為了避免建構
式的父體子體產生誤會,我習慣在子物件的建構式用以下的做法:

class demo extends CI_Controller(){

    function __construct(){
        parent::__construct();
        $this->_init();
    }

    private function _init(){
        //這才是真正寫建構式要預載功能的地方。
    }
 
}

因為這邊還沒講到繼承,上面的code只是說明一個編寫習慣。
這個code在講繼承時會再出現。

了解上面二點在建構式中基於編程式碼易讀易維護的方法後。
相信你們就比較了解建構式在物件中所扮演的角色。

接下來,我要全面的改寫第一段code的內容:

class db{

    public $host = 'localhost';
    public $username = 'root';
    public $password = 'password';
    public $database = 'mydb';
    public $result;

    function __construct(){
        $this->sql_connect();
        $this->sql_database();
        $this->set_db_encode();
    }

    function sql_connect(){
        return @mysql_connect($this->host,$this->username,$this->password);
    }

    function sql_database(){
        return @mysql_select_db($this->database);
    }

    function set_db_encode(){
        return mysql_query("SET NAMES 'utf8'");
    }

    function query($sql_string){
        $result = mysql_query($sql_string);
        $query = new db_query($result);
        return $query;
    }

}

class db_query{
        
    private $result;
    
    function __construct($result){
        $this->result = $result;
    }

    function result(){
        $query = array();
        if($this->result != false){
            while($row = mysql_fetch_object($this->result)){
                $query[] = $row;
            }
            return $query;
        }
        return false;
    }

}
$db = new db;
$query = $db->query("SELECT * FROM `user` WHERE name = 'sam'");
foreach($query->result() as $row){
    echo $row->name.'<br/>';
}

這一段code可能複雜了些。
因為他是使用了預載sql連線以及特別的db_query資料查詢法。
但是你可以發現最後的執行就只是實體化db,然後將sql字串交給db物件的query處理。
最後db的query會回傳一個資料集合的物件。
你可以直接從資料物件中取得資料。
(事實上整個db能做的事情是非常多的,透過query物件來取得各種query或是row是很方便的事情,而db本身也可以直接去處理insert、update、delete…等等三功能。)
我們看到了db物件的建構式來做資料庫連線。
然後將產生的$result傳到query物件,而query物件的建構式就直接指定了$this->result這個屬性的值,對於後續的許許多多處理就很輕易的能夠解決。

後記:這次的課程我想我用了較為複雜的mysql來解釋建構式,也帶了點處理sql方法的意味。
但我想或許有些人覺得複雜了一些些。因此這邊我用比較白話的文字來做個總結:
『__construct()建構式,其目的在於當物件被實體化的同時,預先執行要一同載入或執行的功能』。
另外相關於建構式還有一個叫做解構式的東西__destruct();通常因為物件執行完就會被自動銷毀所以我就沒有提到解構式,將來有機會再稍做說明。
再來有關同名函式也是建構式這件事這邊我就隨便帶過了,因為PHP5開始我們強烈建議使用__construct()而不要使用同物件名的函式。所以下面這個例子也是建構式,但不再建議使用。

class demo{

    function demo(){
             ↑↑↑↑同物件名稱所以這一段也是建構式。
    }

}

明天就要來講繼承了。這也是非常重要的課題。
物件的多型全靠繼承來實作了。

值日生記得擦黑板,倒垃圾。放學回家要乖乖的路上別亂跑。
過馬路要看紅綠燈,記得扶老太太過馬路。


0
老鷹(eagle)
iT邦高手 1 級 ‧ 2013-01-23 14:38:20

謝謝謝謝
感謝SAM大 教學
筆記筆記

tkdmaf提到:
你離題了

話說離題 歪樓好像是本邦習俗哈哈

0
richardsuma
iT邦大師 3 級 ‧ 2013-01-24 16:56:43

謝謝詳細說明!讚

0
0
ted99tw
iT邦高手 1 級 ‧ 2013-02-01 12:16:06

簽名簽名簽名

大大為何不早兩個月來參加鐵人賽了啊~~~

灑花灑花灑花

因為我.............壓根本曉得有這件事。汗OrzXD

不曉得......

0
ilanspeed
iT邦新手 5 級 ‧ 2014-05-04 16:31:53

請問為什麼db_query要預載 __construct 這一段啊?
我測試之後,少了這一段,程式就跑不起來
不太知道這一段的功用是什麼。

&lt;pre class="c" name="code">class db_query{  
          
    private $result;  
      
    function __construct($result){  
        $this->result = $result;  
    }  

還有,為什麼function result()不能直接變成 function result($result),把變數值帶進來?
而要上述的程式才可以成功?

不好意思,不了解這兩個問題,再請好心人仕幫忙回答一下,謝謝。

2
fillano
iT邦超人 1 級 ‧ 2014-05-04 22:14:23

如果是要問為什麼需要獨立出db_query類別,基本上這是「設計」的問題啦。(這個就需要樓主來回答了)

另外,我想樓主只是示範__construct的使用,所以簡化了範例。他設計的範例,在要支援不同資料庫時,會更容易明白為什麼這樣設計。基本上db_query類別可以用來封裝處理各種資料庫處理query result的邏輯,當然這樣設計會複雜一點。

&lt;pre class="c" name="code">
interface idb_query {
    public function result();
}
class mysql_db_query implements idb_query{
    private $result;
    function __construct($result) {
        $this->$result = $result;
    }
    public function result() {
        ....
        return $array;
    }
}

如果是這樣設計,就不會透過result($result)來呼叫,因為對不同的idb_query實作來說,$result是完全不一樣的物件。而對使用者來說,他只需要知道db_query如何操作,並不需要知道$result的型別,這個資訊應該要封裝起來的。所以db_query是在db::query()中生成並回傳。

我要留言

立即登入留言