iT邦幫忙

13

PHP物件導向的第二課:重談「方法」,物件「屬性」及「成員」

早上進公司頭都會昏昏的,有時會想不起來接下來的步驟。這時只好寫一些東西來保持活力。
昨天講完了最基本的class宣告方式以及如果實作物件並執行其所提供的功能。但是我想應該有人覺得怪怪的是說「function」明明叫做「函式」怎麼我會講成「方法」???關於這一點我只能說我實在也是搞不懂為什麼php的物件不採用成員+函式名稱,像如下例子:

//註:因為php的變數及函式回傳值都是「泛型」所以不需要定義回傳值的型態,這算挺特別的。
//還有這段code是不能跑的,不要複製貼上去用喔!
class demo{

    public Hello(){
        echo 'Hello World!';
    }

}

php對於方法的宣告,仍然使用同函式指令的「function」。
雖然我對此有些許微詞但......,我又不是開發php的人員這種事也不是我說了算。
基本上物件的方法(method)也就是物件所提功的功能或是要執行的動作。
對於一個物件基於名詞或形容詞的「屬性」,method很明顯他是屬於「動詞」。

觀眾:你是在教那一國語言?還n、adj跟v的咧。

為什麼我在這地方要很強調「method」這個單字?
這是因為在php對物件的函式庫中,針對方法的一些函式功能他用的關鍵詞就是「method」
例如:get_class_methods(),呼叫類別中的方法名稱。
之前還有人對我說:啊就function啊!物件不是都是寫function。
我只能說:此function非彼function,函式歸函式,方法歸方法,二個是不一樣的東西。
(基於程式設計概念或原則,php讓人不滿的地方還是有的。)

接著下來我們要來談談「屬性」。
上一段講過了,屬性是屬於名詞和形容詞。

觀眾:不是說了叫你不要教語言你還.........

昨天在講一些關於「物件」的基本概念時,我說過屬性像是:我的名字是「sam」,我的性別是「男性」。
這是關於「我」這個物件所能表示出「我」的一種狀態。
你要稱呼我,當然可以用代名詞的你、我、他。
但是如果我自己能有一個具名的名稱讓你可以具體的知道我是誰。
那我的名字就很重要。這邊賦予我的名字、我的一些個人資料等等,都是屬於屬性的一種。
換個方式講到車,一台藍色的、3000cc的、六速自手排的、四門的....
這些都是這台車的「可形容」的詞,也就是關於這台車的屬性:

//屬性宣告
class car{

    public $color;
    public $engine;
    public $transmission;
    public $door;

}






//屬性定義
class car{

    public $color = 'blue';
    public $engine = '3000cc';
    public $transmission = '6speed';
    public $door = 4;

}

上面是屬性定義的方式,第一個範例中,你可以看到我僅有宣告屬性的存在。
但我並沒有宣告屬性的內容。
但是第二個範例中,我每一個屬性都定義了其內容。
通常這樣的差異在於你宣告其屬性但不賦予值,大都表示該屬性是在物件中會被改變其值的。
而你直接賦予其值的屬性,則是在物件中原則上不改變其值的(但通常你還是會有必要去改變他)

這邊比較特別的出現了「public」這樣的指令。
有關public待會會有詳細的說明,public是宣告屬性的其中一種前綴字。

再來就是屬性在物件中的使用方式了。
接下來我們先來看這樣的code:

class demo{
  
    public $hello = 'Hello World';

    function hello(){
        echo $hello;
    }
}   
$demo = new demo;
$demo->hello();

猜猜看,這樣會發生什麼事?

如果你認為答案會跳出Hello World,那很抱歉的。
你應該會獲得一個「變數未定義」的錯誤。
假如你沒有獲得這個錯誤,表示你的php設定error的顯示方式不夠嚴謹。

這問題是出在那裡?
問題出在echo $hello和punic $hello的$hello並不是同一個東西。
你們在學php時,應該有學到所謂的全域變數和區域變數。
主程式及函式中的變數是不通用的,因為屬於各自機區域,除非你去宣告globals。
這樣的概念在物件中是一樣的。
但要注意的事,在這邊不是指這二個$hello因為分屬不同區域所以不同。
而是我要強調的是:public $hello是這個物件的「屬性」。
而不是「變數」。
儘管這個屬性的內容是可以被改變的。

在php的物件中,為了能具體的指出屬性的主體,你必須先告知主體所屬為何。
也因為,在物件中,你必須使用關鍵字「$this」來表示為該物件本體。
注意一下$this並不是一個變數,而是物件的主體關鍵字,無論如何不要拿來做為變數名稱。

所以上述的code要能夠正常運行,你必須如此做:

class demo{
  
    public $hello = 'Hello World';

    function hello(){
        echo $this->hello;
    }
}   
$demo = new demo;
$demo->hello();

這邊的$this->hello屬性對應的就是public $hello的屬性宣告。
而$this指的正是demo這個物件主體。
要注意的是,屬性是屬於「物件本體全域」。
他只要是在這個物件中,任何一個方法(method)都可以直接叫用他。
這也是為什麼物件之於函式來說,某方面而言反而較具彈性。
因為你不需要去對方法內的變數進行「全域宣告」。
(事實上你是可以這樣做,但會變成整個程式都可以使用這個變數)
另外就是如果你是在物件中要呼叫其他的函式,則是使用

$this->otherMethod();

如此即可在物件內去呼叫其他的方法。

上面的範例你如果都執行了也熟悉的差不多。
再來你應該就還是有個疑惑:「public」是什麼意思?

這邊我們就來談談物件中另一個重要的宣告:「成員」
在php的物件中,不論是屬性還是方法,都「可以」定義其成員的性質。
為什麼我是寫「可以」而不是寫「必要」?
這是因為在php來說,如果你沒有事先宣告屬性,事實上仍然是可以直接「額外賦予其值」。
這和php本身的做法有關,在php中,變數或屬性賦值的行為就視為變數或屬性的初始化。

class demo{

    function setProperty(){
        $this->name = 'sam';
    }
}

上面就是一個屬性賦值初始化的例子。
也就是說,即使你一開始沒有定義屬性,你仍然能在程式中「賦予其值使之初始化」。
但是因為你沒有去定義他,所以該屬性會被強制視為「public所宣告的」
要注意我說的是「可以賦值」但我沒說他可以在未宣告下直接使用。
你如果未在一開始宣告屬性,在程式碼中又沒有進行「賦值」的話,那麼你直接使用該屬性還是
會出現屬性未定義的錯誤。
另外,如果你沒有對方法定義成員,則他預設就是public。

在物件中主要定義成員有三種:
public(開放成員):不論在物件本體,或是外部程式,都可以使接使用。
private(私有成員):僅在該物件本體可以使用,外部程式或繼承於本物件之子類別無法使用。
protected(保護成員):僅外部程式無法叫用,但物件本體及繼承的子類別均可使用。

public(開放成員)可以在任何的程式中被設定及使用。
其宣告屬性的方法上面提過就是:

public $property;

而如果你是要宣告方法為開放成員。
其做法是:

public function mymethod(){

}

※這就是我抱怨的地方,public都宣告出來還要多餘的加上function......,不知道將來的
PHP6甚至PHP7有沒有可能去修正這一點。

開放成員可以在物件中,物件外被「使用」及「改變」(相對來說危險性也較高)

class demo{

    public $name = 'sam';

}
$demo = new demo;
echo $demo->name;
$demo->name = 'john';
echo $demo->name;

上面這個例子中,我一開始name屬性定義是sam。
而外部中我echo了這個name屬性。
但是我之後卻把他換成了john。
最後……就會印出john。
因為我定義的是開放成員,所以在外部可以被叫用及改變。

class demo{

    private $name = 'sam';

}
$demo = new demo;
echo $demo->name;
$demo->name = 'john';
echo $demo->name;

上述這個code,我將name屬性改成了私有成員。
因為這個屬性只能在物件中被使用及改變。
所以說最後三行執行的程式會全部異常。
會告知該屬性為私有成員為法被使用。
也因此,如果我希望能印出sam,但是name屬性不被改變的話:

class demo{

    private $_name = 'sam';

    function getName(){
        return $this->_name;
    }

}
$demo = new demo;
echo $demo->getName();

也就是說你必須透過一個開放的函式去傳出一個私有成員的值,因為動作是在物件本體執行所以不會有問題。
這邊請注意一個習慣,通常為了區別開放成員及私有成員,開放成員的屬性及方法就是直接用你所定義的文字,但是如果是私有成員,我們會習慣前面加上一個_(底線)來區別。
請養成這個習慣。

再來一個例子就是說,假設一台車要起步,必須發動引擎、踩離合器、換檔、踩油門:

class car{

    function Action(){
        $this->startEngine();
        $this->clutch();
        $this->transMission();
        $this->accelerator();
    }

    function startEngine(){...}
    function clutch(){...}
    function transMission(){...}
    function accelerator(){...}
    
}
$car = new car;
$car->Action();

上述這個例子全都是開放成員。
這會造成很可怕的結果,就是你可以不經過正確的動做去使用其他的方式。
結果就是車子可能因此被弄壞。
因此合理的我們應該要將不是正常該被直接執行的行為設定為私有成員。
如此才能保護程式的正常運作。

class car{

    function Action(){
        $this->_startEngine();
        $this->_clutch();
        $this->_transMission();
        $this->_accelerator();
    }

    private function _startEngine(){...}
    private function _clutch(){...}
    private function _transMission(){...}
    private function _accelerator(){...}
    
}
$car = new car;
$car->Action();

好了!今天的課就上到這邊。
下一堂課我要來講建構式及繼承。
我知道有人問為什麼這次沒講protected。
因為要跟繼承一起講會比較好。

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


0
老鷹(eagle)
iT邦高手 1 級 ‧ 2013-01-22 11:31:47

謝謝 SAM大 教學~~!
謝謝謝謝
沙發沙發

0
fillano
iT邦超人 1 級 ‧ 2013-01-22 11:55:51

這些比較累贅的語法,應該是歷史因素...

在php4,class並不支援「可視性」,所以class是這樣定義

<pre class="c" name="code">
class A {
    var $member;
    function method(){}
}

這樣的code在php5還是能執行,沒有加可視性修飾時,member跟method預設就是public。

不過其實constructor已經不相容了,就看以後的核心團隊是否會改這些。

參考:http://php.net/manual/en/language.oop5.visibility.php裡面的一個note。

我是覺得啦。
要嘛就採行二種規格共生,但建議使用新規格的做法。
就像php5雖然向下支援php的class,但是不再建議使用var定議屬性。
也不建議使用和物件同名稱的函式來定議建構式。

上面缺字……
就像php5雖然向下支援php4的class,但是不再建議使用var定議屬性。

fillano iT邦超人 1 級 ‧ 2013-01-22 17:19:36 檢舉

嗯嗯,不過既然不建議...想必以後會拿掉...只是不知道要隔多久XD

我要留言

立即登入留言