PHP物件導向中的重要知識點(二)

OrangeAdmin發表於2014-01-02

1. __toString:

    當物件被列印時,如果該類定義了該方法,則列印該方法的返回值,否則將按照PHP的預設行為輸出列印結果。該方法類似於Java中的toString()。

<?php
class TestClass {
    public function __toString() {
        return "This is TestClass::__toString.\n";
    }
}

$testObj = new TestClass();
print $testObj;

    執行結果如下:

Stephens-Air:Desktop$ php Test.php 
This is TestClass::__toString.

2. __get和__set:

    這兩個方法用於處理類中未宣告的屬性訪問。當物件使用者試圖訪問未宣告的物件屬性時,__get()會被呼叫,並帶有一個包含要訪問的屬性名稱字串作為引數。無論從__get()方法返回什麼,都會直接返回給呼叫者,就如同帶有該值的屬性存在一樣。另外需要注意的是,如果屬性存在,但是其訪問可見性為private或protected,那麼這兩個攔截方法同樣會被呼叫,反之,如果屬性存在切可訪問,那麼直接訪問屬性即可,這兩個方法將不再會被呼叫。以下為__get()攔截方法的示例程式碼:

<?php
class TestClass {
    private $privateField;
    public $publicField;
    public function __construct() {
        $this->privateField = "This is a private Field.\n";
        $this->publicField = "This is a public Field.\n";
    }

    public function __get($property) {
        print "__get() is called.\n";
        $method = "get${property}";
        if (method_exists($this, $method)) {
            return $this->$method();
        }
        return "This is undefined field.\n";
    }
    public function getPrivateField() {
        return $this->privateField;
    }
}

$testObj = new TestClass();
print $testObj->privateField;
print $testObj->undefinedField;
print $testObj->publicField;

    執行結果如下:

Stephens-Air:Desktop$ php Test.php 
__get() is called.
This is a private Field.
__get() is called.
This is undefined field.
This is a public Field.

    __set()方法被呼叫的規則和__get()基本相同,差別是用於攔截未定義或不可見類屬性的賦值操作。另外,該方法接收兩個引數,分別是屬性名稱和要設定的值。見如下程式碼示例:

<?php
class TestClass {
    private $privateField;
    public $publicField;
    public function __construct() {
        $this->privateField = "This is a private Field.\n";
        $this->publicField = "This is a public Field.\n";
    }
    public function __get($property) {
        print "__get() is called.\n";
        $method = "get${property}";
        if (method_exists($this, $method)) {
            return $this->$method();
        }
        return "This is an undefined field.\n";
    }
    public function __set($property, $value) {
        print "__set is called.\n";
        $method = "set${property}";
        if (method_exists($this, $method)) {
            $this->$method($value);
        } else {
            print "This is an undefined field.\n";
        }
    }
    public function getPrivateField() {
        return $this->privateField;
    }
    public function setPrivateField($value) {
        $this->privateField = $value;
    }
}

$testObj = new TestClass();
$testObj->privateField = "This is a private Field after set.\n";
$testObj->undefinedField = "This is a undefined Field after set.\n";
$testObj->publicField = "This is a public Field after set.\n";

print $testObj->privateField;
print $testObj->undefinedField;
print $testObj->publicField;

    執行結果如下:

Stephens-Air:Desktop$ php Test.php 
__set is called.
__set is called.
This is an undefined field.
__get() is called.
This is a private Field after set.
__get() is called.
This is an undefined field.
This is a public Field after set.

3. __isset和__unset:

    這兩個攔截方法被呼叫的規則和__get()和__set()非常類似,只是用於類中不存在或不可見屬性被isset()和unset()兩個全域性方法應用時才會被分別觸發。 

<?php
class TestClass {
    private $privateField;
    public $publicField;
    public function __construct() {
        $this->privateField = "Defined private field";
        $this->publicField = "Defined public field";
    }
    public function __isset($property) {
        print "__isset is called.\n";
        return isset($this->$property);
    }
    public function __unset($property) {
        print "__unset is called.\n";
        if (isset($this->$property)) {
            unset($this->$property);
        }
    }
}

$testObj = new TestClass();
print 'isset($testObj->privateField) is '.(isset($testObj->privateField) ? "true" : "false")."\n";
print 'isset($testObj->undefinedField) is '.(isset($testObj->undefinedField) ? "true" : "false")."\n";
print 'isset($testObj->publicField) is '.(isset($testObj->publicField) ? "true" : "false")."\n";

print "After unset......\n";
//下面兩個函式呼叫後,$testObj的兩個物件屬性均會變為不可用。
//另外從輸出結果來看,__unset方法僅僅被呼叫一次,因為publicField為可見屬性,所以__unset不會因該屬性而被呼叫。
unset($testObj->privateField);
unset($testObj->publicField);

print 'isset($testObj->privateField) is '.(isset($testObj->privateField) ? "true" : "false")."\n";
print 'isset($testObj->publicField) is '.(isset($testObj->publicField) ? "true" : "false")."\n";

    執行結果如下:

Stephens-Air:Desktop$ php Test.php 
__isset is called.
isset($testObj->privateField) is true
__isset is called.
isset($testObj->undefinedField) is false
isset($testObj->publicField) is true
After unset......
__unset is called.
__isset is called.
isset($testObj->privateField) is false
__isset is called.
isset($testObj->publicField) is false

4. __call:

    __call()方法是一個非常有用但又非常容易被濫用的攔截方法。當物件使用者試圖訪問當前物件未定義的成員函式時,__call()會被自動呼叫,同時傳遞兩個引數,分別為函式名稱和傳遞給呼叫函式的所有引數(陣列)。__call方法返回的任何值都會返回給函式呼叫者,就如同該成員函式真實存在一樣。下面給出一個非常有用的委託示例。 

<?php
class DelegateClass {
    function printMessage($arg1, $arg2) {
        print "DelegateClass:delegatedMethod is called.\n";
        print '$arg1 = '.$arg1.'and $arg2 = '.$arg2."\n";
    }
}
class TestClass {
    private $delegateObj;
    public function __construct() {
        $this->delegateObj = new DelegateClass();
    }
    public function __call($method, $args) {
        $this->delegateObj->$method($args[0],$args[1]);
    }
}

$testObj = new TestClass();
$testObj->printMessage("hello","world");

    執行結果如下:

Stephens-Air:Desktop$ php Test.php 
DelegateClass:delegatedMethod is called.
$arg1 = helloand $arg2 = world

    從以上示例可以看出,TestClass並未宣告printMessage成員方法,但是透過__call()方法的巧妙橋接直接傳遞給了委託物件。個人認為該技巧為雙刃劍,切勿過度使用。

5. 回撥函式: 

    回撥函式的應用場景無須多述,在C/C++中充斥著無數的回撥函式典型用例。 這裡只是簡單給出PHP中回撥函式的使用規則。見如下示例程式碼和關鍵性註釋: 

<?php
class Product {
    public $name;
    public $price;
    public function __construct($name, $price) {
        $this->name = $name;
        $this->price = $price;
    }
}

class ProcessSale {
    private $callbacks;
    function registerCallback($cb) {
        if (!is_callable($cb)) {
            throw new Exception("callback not callable.");
        }
        $this->callbacks[] = $cb;
    }
    function sale($product) {
        print "{$product->name}: processing \n";
        foreach ($this->callbacks as $cb) {
            //以下兩種呼叫方式均可。
            call_user_func($cb, $product);
            $cb($product);
        }
    }
}

$logger = function($product) {
    print "    logging ({$product->name})\n";
};

$processor = new ProcessSale();
$processor->registerCallback($logger);
$processor->sale(new Product("shoes",6));
print "\n";
$processor->sale(new Product("coffee",6));

    執行結果如下:

Stephens-Air:Desktop$ php Test.php 
shoes: processing 
    logging (shoes)
    logging (shoes)

coffee: processing 
    logging (coffee)
    logging (coffee)

6. use(閉包):

    在Javascript中存在大量的閉包應用,PHP中的閉包則是透過use關鍵字來完成的。對於閉包這個概念本身而言,簡要的說就是函式內的程式碼可以訪問其父作用域中的變數。見如下示例程式碼和關鍵性註釋:

<?php
class Product {
    public $name;
    public $price;
    public function __construct($name, $price) {
        $this->name = $name;
        $this->price = $price;
    }
}

class ProcessSale {
    private $callbacks;
    function registerCallback($cb) {
        if (!is_callable($cb)) {
            throw new Exception("callback not callable.");
        }
        $this->callbacks[] = $cb;
    }
    function sale($product) {
        print "{$product->name}: processing \n";
        foreach ($this->callbacks as $cb) {
            $cb($product);
        }
    }
}

class Totalizer {
    static function warnAmount($amt) {
        $count = 0;
        //注意這裡的$amt和$count均為閉包變數,其中&$count是以引用的形式傳遞的,即一旦函式內部修改了該變數的值,
        //那麼下次再訪問該閉包變數時,$count將為之前呼叫中修改後的值。
        return function($product) use($amt, &$count) {
            $count += $product->price;
            print "     count: $count\n";
            if ($count > $amt) {
                print "     high price reached: {$count}\n";
            }
        };
    }
}

$processor = new ProcessSale();
$processor->registerCallback(Totalizer::warnAmount(8));
$processor->sale(new Product("shoes",6));
$processor->sale(new Product("coffee",6));

    執行結果如下:

shoes: processing 
     count: 6
coffee: processing 
     count: 12
     high price reached: 12

注:該Blog中記錄的知識點,是在我學習PHP的過程中,遇到的一些PHP和其他面嚮物件語言相比比較獨特的地方,或者是對我本人而言確實需要簿記下來以備後查的知識點。雖然談不上什麼深度,但是還是希望能與大家分享。

相關文章