PHP V5 新的面向?qū)ο缶幊烫匦燥@著提升了這個(gè)流行語(yǔ)言中的功能層次。學(xué)習(xí)如何用 PHP V5 動(dòng)態(tài)特性創(chuàng)建可以滿足需求的對(duì)象。
PHP V5 中新的面向?qū)ο缶幊蹋∣OP)特性的引入顯著提升了這個(gè)編程語(yǔ)言的功能層次。現(xiàn)在不僅有了私有的、受保護(hù)的和公共的成員變量和函數(shù) —— 就像在 Java?、 C++ 或 C# 編程語(yǔ)言中一樣 —— 但是還可以創(chuàng)建在運(yùn)行時(shí)變化的對(duì)象,即動(dòng)態(tài)地創(chuàng)建新方法和成員變量。而使用 Java、C++ 或 C# 語(yǔ)言是做不到這件事的。這種功能使得超級(jí)快速的應(yīng)用程序開(kāi)發(fā)系統(tǒng)(例如 Ruby on Rails)成為可能。
但是,在進(jìn)入這些之前,有一點(diǎn)要注意:本文介紹 PHP V5 中非常高級(jí)的 OOP 特性的使用,但是這類特性不是在每個(gè)應(yīng)用程序中都需要的。而且,如果不具備 OOP 的堅(jiān)實(shí)基礎(chǔ)以及 PHP 對(duì)象語(yǔ)法的初步知識(shí),這類特性將會(huì)很難理解。
動(dòng)態(tài)的重要性
對(duì)象是把雙刃劍。一方面,對(duì)象是封裝數(shù)據(jù)和邏輯并創(chuàng)建更容易維護(hù)的系統(tǒng)的重大方式。但另一方面,它們會(huì)變得很繁瑣,需要許多冗余的代碼,這時(shí)可能最希望做到的就是不要犯錯(cuò)。這類問(wèn)題的一個(gè)示例來(lái)自數(shù)據(jù)庫(kù)訪問(wèn)對(duì)象。一般來(lái)說(shuō),想用一個(gè)類代表每個(gè)數(shù)據(jù)庫(kù)表,并執(zhí)行以下功能:對(duì)象從數(shù)據(jù)庫(kù)讀出數(shù)據(jù)行;允許更新字段,然后用新數(shù)據(jù)更新數(shù)據(jù)庫(kù)或刪除行。還有一種方法可以創(chuàng)建新的空對(duì)象,設(shè)置對(duì)象的字段,并把數(shù)據(jù)插入數(shù)據(jù)庫(kù)。
如果在數(shù)據(jù)庫(kù)中有一個(gè)表,名為 Customers,那么就應(yīng)當(dāng)有一個(gè)對(duì)象,名為 Customer
,它應(yīng)當(dāng)擁有來(lái)自表的字段,并代表一個(gè)客戶。而且 Customer
對(duì)象應(yīng)當(dāng)允許插入、更新或刪除數(shù)據(jù)庫(kù)中對(duì)應(yīng)的記錄。現(xiàn)在,一切都很好,而且有也很多意義。但是,有許多代碼要編寫(xiě)。如果在數(shù)據(jù)庫(kù)中有 20 個(gè)表,就需要 20 個(gè)類。
有三個(gè)解決方案可以采用。第一個(gè)解決方案就是,坐在鍵盤(pán)前,老老實(shí)實(shí)地錄入一段時(shí)間。對(duì)于小項(xiàng)目來(lái)說(shuō),這還可以,但是我很懶。第二個(gè)解決方案是用代碼生成器,讀取數(shù)據(jù)庫(kù)模式,并自動(dòng)編寫(xiě)代碼。這是個(gè)好主意,而且是另一篇文章的主題。第三個(gè)解決方案,也是我在本文中介紹的,是編寫(xiě)一個(gè)類,在運(yùn)行時(shí)動(dòng)態(tài)地把自己塑造成指定表的字段。這個(gè)類執(zhí)行起來(lái)比起特定于表的類可能有點(diǎn)慢 —— 但是把我從編寫(xiě)大量代碼中解脫出來(lái)。這個(gè)解決方案在項(xiàng)目開(kāi)始的時(shí)候特別有用,因?yàn)檫@時(shí)表和字段不斷地變化,所以跟上迅速的變化是至關(guān)重要的。
所以,如何才能編寫(xiě)一個(gè)能夠彎曲 的類呢?
寫(xiě)一個(gè)柔性的類
對(duì)象有兩個(gè)方面:成員變量 和方法。在編譯語(yǔ)言(例如 Java)中,如果想調(diào)用不存在的方法或引用不存在的成員變量,會(huì)得到編譯時(shí)錯(cuò)誤。但是,在非編譯語(yǔ)言,例如 PHP 中,會(huì)發(fā)生什么?
在 PHP 中的方法調(diào)用是這樣工作的。首先,PHP 解釋器在類上查找方法。如果方法存在,PHP 就調(diào)用它。如果沒(méi)有,那么就調(diào)用類上的魔法方法 __call
(如果這個(gè)方法存在的話)。如果 __call
失敗,就調(diào)用父類方法,依此類推。
|
魔法方法
魔法方法是有特定名稱的方法,PHP 解釋器在腳本執(zhí)行的特定點(diǎn)上會(huì)查找魔法方法。最常見(jiàn)的魔法方法就是對(duì)象創(chuàng)始時(shí)調(diào)用的構(gòu)造函數(shù)。
|
|
__call
方法有兩個(gè)參數(shù):被請(qǐng)求的方法的名稱和方法參數(shù)。如果創(chuàng)建的 __call
方法接受這兩個(gè)參數(shù),執(zhí)行某項(xiàng)功能,然后返回 TRUE,那么調(diào)用這個(gè)對(duì)象的代碼就永遠(yuǎn)不會(huì)知道在有代碼的方法和 __call
機(jī)制處理的方法之間的區(qū)別。通過(guò)這種方式,可以創(chuàng)建這樣的對(duì)象,即動(dòng)態(tài)地模擬擁有無(wú)數(shù)方法的情況。
除了 __call
方法,其他魔法方法 —— 包括 __get
和 __set
—— 調(diào)用它們的時(shí)候,都是因?yàn)橐昧瞬淮嬖诘膶?shí)例變量。腦子里有了這個(gè)概念之后,就可以開(kāi)始編寫(xiě)能夠適應(yīng)任何表的動(dòng)態(tài)數(shù)據(jù)庫(kù)訪問(wèn)類了。
經(jīng)典的數(shù)據(jù)庫(kù)訪問(wèn)
先從一個(gè)簡(jiǎn)單的數(shù)據(jù)庫(kù)模式開(kāi)始。清單 1 所示的模式針對(duì)的是單一的數(shù)據(jù)表數(shù)據(jù)庫(kù),容納圖書(shū)列表。
清單 1. MySQL 數(shù)據(jù)庫(kù)模式
DROP TABLE IF EXISTS book;
CREATE TABLE book (
book_id INT NOT NULL AUTO_INCREMENT,
title TEXT,
publisher TEXT,
author TEXT,
PRIMARY KEY( book_id )
);
|
請(qǐng)把這個(gè)模式裝入到名為 bookdb 的數(shù)據(jù)庫(kù)。
接下來(lái),編寫(xiě)一個(gè)常規(guī)的數(shù)據(jù)庫(kù)類,然后再把它修改成動(dòng)態(tài)的。清單 2 顯示了圖書(shū)表的簡(jiǎn)單的數(shù)據(jù)庫(kù)訪問(wèn)類。
清單 2. 基本的數(shù)據(jù)庫(kù)訪問(wèn)客戶機(jī)
<?php
require_once("DB.php");
$dsn = 'mysql://root:password@localhost/bookdb';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
class Book
{
private $book_id;
private $title;
private $author;
private $publisher;
function __construct()
{
}
function set_title( $title ) { $this->title = $title; }
function get_title( ) { return $this->title; }
function set_author( $author ) { $this->author = $author; }
function get_author( ) { return $this->author; }
function set_publisher( $publisher ) {
$this->publisher = $publisher; }
function get_publisher( ) { return $this->publisher; }
function load( $id )
{
global $db;
$res = $db->query( "SELECT * FROM book WHERE book_id=?",
array( $id ) );
$res->fetchInto( $row, DB_FETCHMODE_ASSOC );
$this->book_id = $id;
$this->title = $row['title'];
$this->author = $row['author'];
$this->publisher = $row['publisher'];
}
function insert()
{
global $db;
$sth = $db->prepare(
'INSERT INTO book ( book_id, title, author, publisher )
VALUES ( 0, ?, ?, ? )'
);
$db->execute( $sth,
array( $this->title,
$this->author,
$this->publisher ) );
$res = $db->query( "SELECT last_insert_id()" );
$res->fetchInto( $row );
return $row[0];
}
function update()
{
global $db;
$sth = $db->prepare(
'UPDATE book SET title=?, author=?, publisher=?
WHERE book_id=?'
);
$db->execute( $sth,
array( $this->title,
$this->author,
$this->publisher,
$this->book_id ) );
}
function delete()
{
global $db;
$sth = $db->prepare(
'DELETE FROM book WHERE book_id=?'
);
$db->execute( $sth,
array( $this->book_id ) );
}
function delete_all()
{
global $db;
$sth = $db->prepare( 'DELETE FROM book' );
$db->execute( $sth );
}
}
$book = new Book();
$book->delete_all();
$book->set_title( "PHP Hacks" );
$book->set_author( "Jack Herrington" );
$book->set_publisher( "O'Reilly" );
$id = $book->insert();
echo ( "New book id = $id\n" );
$book2 = new Book();
$book2->load( $id );
echo( "Title = ".$book2->get_title()."\n" );
$book2->delete( );
?>
|
為了保持代碼簡(jiǎn)單,我把類和測(cè)試代碼放在一個(gè)文件中。文件首先得到數(shù)據(jù)庫(kù)句柄,句柄保存在一個(gè)全局變量中。然后定義 Book
類,用私有成員變量代表每個(gè)字段。還包含了一套用來(lái)從數(shù)據(jù)庫(kù)裝入、插入、更新和刪除行的方法。
底部的測(cè)試代碼先刪除數(shù)據(jù)庫(kù)中的所有條目。然后,代碼插入一本書(shū),輸出新記錄的 ID。然后,代碼把這本書(shū)裝入另一個(gè)對(duì)象并輸出書(shū)名。
清單 3 顯示了在命令行上用 PHP 解釋器運(yùn)行代碼的效果。
清單 3. 在命令行運(yùn)行代碼
% php db1.php
New book id = 25
Title = PHP Hacks
%
|
不需要看太多,就已經(jīng)得到重點(diǎn)了。Book
對(duì)象代表圖書(shū)數(shù)據(jù)表中的行。通過(guò)使用上面的字段和方法,可以創(chuàng)建新行、更新行和刪除行。
初識(shí)動(dòng)態(tài)
下一步是讓類變得稍微動(dòng)態(tài)一些:動(dòng)態(tài)地為每個(gè)字段創(chuàng)建 get_
和 set_
方法。清單 4 顯示了更新后的代碼。
清單 4. 動(dòng)態(tài) get_ 和 set_ 方法
<?php
require_once("DB.php");
$dsn = 'mysql://root:password@localhost/bookdb';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
class Book
{
private $book_id;
private $fields = array();
function __construct()
{
$this->fields[ 'title' ] = null;
$this->fields[ 'author' ] = null;
$this->fields[ 'publisher' ] = null;
}
function __call( $method, $args )
{
if ( preg_match( "/set_(.*)/", $method, $found ) )
{
if ( array_key_exists( $found[1], $this->fields ) )
{
$this->fields[ $found[1] ] = $args[0];
return true;
}
}
else if ( preg_match( "/get_(.*)/", $method, $found ) )
{
if ( array_key_exists( $found[1], $this->fields ) )
{
return $this->fields[ $found[1] ];
}
}
return false;
}
function load( $id )
{
global $db;
$res = $db->query( "SELECT * FROM book WHERE book_id=?",
array( $id ) );
$res->fetchInto( $row, DB_FETCHMODE_ASSOC );
$this->book_id = $id;
$this->set_title( $row['title'] );
$this->set_author( $row['author'] );
$this->set_publisher( $row['publisher'] );
}
function insert()
{
global $db;
$sth = $db->prepare(
'INSERT INTO book ( book_id, title, author, publisher )
VALUES ( 0, ?, ?, ? )'
);
$db->execute( $sth,
array( $this->get_title(),
$this->get_author(),
$this->get_publisher() ) );
$res = $db->query( "SELECT last_insert_id()" );
$res->fetchInto( $row );
return $row[0];
}
function update()
{
global $db;
$sth = $db->prepare(
'UPDATE book SET title=?, author=?, publisher=?
WHERE book_id=?'
);
$db->execute( $sth,
array( $this->get_title(),
$this->get_author(),
$this->get_publisher(),
$this->book_id ) );
}
function delete()
{
global $db;
$sth = $db->prepare(
'DELETE FROM book WHERE book_id=?'
);
$db->execute( $sth,
array( $this->book_id ) );
}
function delete_all()
{
global $db;
$sth = $db->prepare( 'DELETE FROM book' );
$db->execute( $sth );
}
}
..
|
要做這個(gè)變化,需要做兩件事。首先,必須把字段從單個(gè)實(shí)例變量修改成字段和值組合構(gòu)成的散列表。然后必須添加一個(gè) __call
方法,它只查看方法名稱,看方法是 set_
還是 get_
方法,然后在散列表中設(shè)置適當(dāng)?shù)淖侄巍?/font>
注意,load
方法通過(guò)調(diào)用 set_title
、set_author
和 set_publisher
方法 —— 實(shí)際上都不存在 —— 來(lái)實(shí)際使用 __call
方法。
走向完全動(dòng)態(tài)
刪除 get_
和 set_
方法只是一個(gè)起點(diǎn)。要?jiǎng)?chuàng)建完全動(dòng)態(tài)的數(shù)據(jù)庫(kù)對(duì)象,必須向類提供表和字段的名稱,還不能有硬編碼的引用。清單 5 顯示了這個(gè)變化。
清單 5. 完全動(dòng)態(tài)的數(shù)據(jù)庫(kù)對(duì)象類
<?php
require_once("DB.php");
$dsn = 'mysql://root:password@localhost/bookdb';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
class DBObject
{
private $id = 0;
private $table;
private $fields = array();
function __construct( $table, $fields )
{
$this->table = $table;
foreach( $fields as $key )
$this->fields[ $key ] = null;
}
function __call( $method, $args )
{
if ( preg_match( "/set_(.*)/", $method, $found ) )
{
if ( array_key_exists( $found[1], $this->fields ) )
{
$this->fields[ $found[1] ] = $args[0];
return true;
}
}
else if ( preg_match( "/get_(.*)/", $method, $found ) )
{
if ( array_key_exists( $found[1], $this->fields ) )
{
return $this->fields[ $found[1] ];
}
}
return false;
}
function load( $id )
{
global $db;
$res = $db->query(
"SELECT * FROM ".$this->table." WHERE ".
$this->table."_id=?",
array( $id )
);
$res->fetchInto( $row, DB_FETCHMODE_ASSOC );
$this->id = $id;
foreach( array_keys( $row ) as $key )
$this->fields[ $key ] = $row[ $key ];
}
function insert()
{
global $db;
$fields = $this->table."_id, ";
$fields .= join( ", ", array_keys( $this->fields ) );
$inspoints = array( "0" );
foreach( array_keys( $this->fields ) as $field )
$inspoints []= "?";
$inspt = join( ", ", $inspoints );
$sql = "INSERT INTO ".$this->table." ( $fields )
VALUES ( $inspt )";
$values = array();
foreach( array_keys( $this->fields ) as $field )
$values []= $this->fields[ $field ];
$sth = $db->prepare( $sql );
$db->execute( $sth, $values );
$res = $db->query( "SELECT last_insert_id()" );
$res->fetchInto( $row );
$this->id = $row[0];
return $row[0];
}
function update()
{
global $db;
$sets = array();
$values = array();
foreach( array_keys( $this->fields ) as $field )
{
$sets []= $field.'=?';
$values []= $this->fields[ $field ];
}
$set = join( ", ", $sets );
$values []= $this->id;
$sql = 'UPDATE '.$this->table.' SET '.$set.
' WHERE '.$this->table.'_id=?';
$sth = $db->prepare( $sql );
$db->execute( $sth, $values );
}
function delete()
{
global $db;
$sth = $db->prepare(
'DELETE FROM '.$this->table.' WHERE '.
$this->table.'_id=?'
);
$db->execute( $sth,
array( $this->id ) );
}
function delete_all()
{
global $db;
$sth = $db->prepare( 'DELETE FROM '.$this->table );
$db->execute( $sth );
}
}
$book = new DBObject( 'book', array( 'author',
'title', 'publisher' ) );
$book->delete_all();
$book->set_title( "PHP Hacks" );
$book->set_author( "Jack Herrington" );
$book->set_publisher( "O'Reilly" );
$id = $book->insert();
echo ( "New book id = $id\n" );
$book->set_title( "Podcasting Hacks" );
$book->update();
$book2 = new DBObject( 'book', array( 'author',
'title', 'publisher' ) );
$book2->load( $id );
echo( "Title = ".$book2->get_title()."\n" );
$book2->delete( );
? >
|
在這里,把類的名稱從 Book
改成 DBObject
。然后,把構(gòu)造函數(shù)修改成接受表的名稱和表中字段的名稱。之后,大多數(shù)變化發(fā)生在類的方法中,過(guò)去使用一些硬編碼結(jié)構(gòu)化查詢語(yǔ)言(SQL),現(xiàn)在則必須用表和字段的名稱動(dòng)態(tài)地創(chuàng)建 SQL 字符串。
代碼的惟一假設(shè)就是只有一個(gè)主鍵字段,而且這個(gè)字段的名稱是表名加上 _id
。所以,在 book
表這個(gè)示例中,有一個(gè)主鍵字段叫做 book_id
。主鍵的命名標(biāo)準(zhǔn)可能不同;如果這樣,需要修改代碼以符合標(biāo)準(zhǔn)。
這個(gè)類比最初的 Book
類復(fù)雜得多。但是,從類的客戶的角度來(lái)看,這個(gè)類用起來(lái)仍很簡(jiǎn)單。也就是說(shuō),我認(rèn)為這個(gè)類能更簡(jiǎn)單。具體來(lái)說(shuō),我不愿意每次創(chuàng)建圖書(shū)的時(shí)候都要指定表和字段的名稱。如果我四處拷貝和粘貼這個(gè)代碼,然后修改了 book 表的字段結(jié)構(gòu),那么我可能就麻煩了。在清單 6 中,通過(guò)創(chuàng)建一個(gè)繼承自 DBObject
的簡(jiǎn)單 Book
類,我解決了這個(gè)問(wèn)題。
清單 6. 新的 Book 類
..
class Book extends DBObject
{
function __construct()
{
parent::__construct( 'book',
array( 'author', 'title', 'publisher' ) );
}
}
$book = new Book( );
$book->delete_all();
$book->{'title'} = "PHP Hacks";
$book->{'author'} = "Jack Herrington";
$book->{'publisher'} = "O'Reilly";
$id = $book->insert();
echo ( "New book id = $id\n" );
$book->{'title'} = "Podcasting Hacks";
$book->update();
$book2 = new Book( );
$book2->load( $id );
echo( "Title = ".$book2->{'title'}."\n" );
$book2->delete( );
?>
|
現(xiàn)在,Book
類真的是簡(jiǎn)單了。而且 Book
類的客戶也不再需要知道表或字段的名稱了。
改進(jìn)的空間
對(duì)這個(gè)動(dòng)態(tài)類我想做的最后一個(gè)改進(jìn),是用成員變量訪問(wèn)字段,而不是用笨重的 get_
和 set_
操作符。清單 7 顯示了如何用 __get
和 __set
魔法方法代替 __call
。
清單 7. 使用 __get 和 __set 方法
<?php
require_once("DB.php");
$dsn = 'mysql://root:password@localhost/bookdb';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
class DBObject
{
private $id = 0;
private $table;
private $fields = array();
function __construct( $table, $fields )
{
$this->table = $table;
foreach( $fields as $key )
$this->fields[ $key ] = null;
}
function __get( $key )
{
return $this->fields[ $key ];
}
function __set( $key, $value )
{
if ( array_key_exists( $key, $this->fields ) )
{
$this->fields[ $key ] = $value;
return true;
}
return false;
}
function load( $id )
{
global $db;
$res = $db->query(
"SELECT * FROM ".$this->table." WHERE ".
$this->table."_id=?",
array( $id )
);
$res->fetchInto( $row, DB_FETCHMODE_ASSOC );
$this->id = $id;
foreach( array_keys( $row ) as $key )
$this->fields[ $key ] = $row[ $key ];
}
function insert()
{
global $db;
$fields = $this->table."_id, ";
$fields .= join( ", ", array_keys( $this->fields ) );
$inspoints = array( "0" );
foreach( array_keys( $this->fields ) as $field )
$inspoints []= "?";
$inspt = join( ", ", $inspoints );
$sql = "INSERT INTO ".$this->table.
" ( $fields ) VALUES ( $inspt )";
$values = array();
foreach( array_keys( $this->fields ) as $field )
$values []= $this->fields[ $field ];
$sth = $db->prepare( $sql );
$db->execute( $sth, $values );
$res = $db->query( "SELECT last_insert_id()" );
$res->fetchInto( $row );
$this->id = $row[0];
return $row[0];
}
function update()
{
global $db;
$sets = array();
$values = array();
foreach( array_keys( $this->fields ) as $field )
{
$sets []= $field.'=?';
$values []= $this->fields[ $field ];
}
$set = join( ", ", $sets );
$values []= $this->id;
$sql = 'UPDATE '.$this->table.' SET '.$set.
' WHERE '.$this->table.'_id=?';
$sth = $db->prepare( $sql );
$db->execute( $sth, $values );
}
function delete()
{
global $db;
$sth = $db->prepare(
'DELETE FROM '.$this->table.' WHERE '.
$this->table.'_id=?'
);
$db->execute( $sth,
array( $this->id ) );
}
function delete_all()
{
global $db;
$sth = $db->prepare( 'DELETE FROM '.$this->table );
$db->execute( $sth );
}
}
class Book extends DBObject
{
function __construct()
{
parent::__construct( 'book',
array( 'author', 'title', 'publisher' ) );
}
}
$book = new Book( );
$book->delete_all();
$book->{'title'} = "PHP Hacks";
$book->{'author'} = "Jack Herrington";
$book->{'publisher'} = "O'Reilly";
$id = $book->insert();
echo ( "New book id = $id\n" );
$book->{'title'} = "Podcasting Hacks";
$book->update();
$book2 = new Book( );
$book2->load( $id );
echo( "Title = ".$book2->{'title'}."\n" );
$book2->delete( );
?>
|
底部的測(cè)試代碼只演示了這個(gè)語(yǔ)法干凈了多少。要得到圖書(shū)的書(shū)名,只需得到 title
成員變量。這個(gè)變量會(huì)調(diào)用對(duì)象的 __get
方法,在散列表中查找 title
條目并返回。
現(xiàn)在就得到了單個(gè)動(dòng)態(tài)的數(shù)據(jù)庫(kù)訪問(wèn)類,它能夠讓自己適應(yīng)到數(shù)據(jù)庫(kù)中的任何表。
動(dòng)態(tài)類的更多用途
編寫(xiě)動(dòng)態(tài)類不僅限于數(shù)據(jù)庫(kù)訪問(wèn)。請(qǐng)看清單 8 中的 Customer
對(duì)象這個(gè)例子。
清單 8. 簡(jiǎn)單的 Customer 對(duì)象
<?php
class Customer
{
private $name;
function set_name( $value )
{
$this->name = $value;
}
function get_name()
{
return $this->name;
}
}
$c1 = new Customer();
$c1->set_name( "Jack" );
$name = $c1->get_name();
echo( "name = $name\n" );
?>
|
這個(gè)對(duì)象足夠簡(jiǎn)單。但是如果我想在每次檢索或設(shè)置客戶名稱時(shí)都記錄日志,會(huì)發(fā)生什么呢?我可以把這個(gè)對(duì)象包裝在一個(gè)動(dòng)態(tài)日志對(duì)象內(nèi),這個(gè)對(duì)象看起來(lái)像 Customer
對(duì)象,但是會(huì)把 get
或 set
操作的通知發(fā)送給日志。清單 9 顯示了這類包裝器對(duì)象。
清單 9. 動(dòng)態(tài)包裝器對(duì)象
<?php
class Customer
{
private $name;
function set_name( $value )
{
$this->name = $value;
}
function get_name()
{
return $this->name;
}
}
class Logged
{
private $obj;
function __call( $method, $args )
{
echo( "$method( ".join( ",", $args )." )\n" );
return call_user_func_array(array(&$this->obj,
$method), $args );
}
function __construct( $obj )
{
$this->obj = $obj;
}
}
$c1 = new Logged( new Customer() );
$c1->set_name( "Jack" );
$name = $c1->get_name();
echo( "name = $name\n" );
?>
|
調(diào)用日志版本的 Customer
的代碼看起來(lái)與前面相同,但是這時(shí),對(duì) Customer
對(duì)象的任何訪問(wèn)都被記入日志。清單 10 顯示了運(yùn)行這個(gè)日志版代碼時(shí)輸出的日志。
清單 10. 運(yùn)行日志版對(duì)象
% php log2.php
set_name( Jack )
get_name( )
name = Jack
%
|
在這里,日志輸出表明用參數(shù) Jack
調(diào)用了set_name
方法。然后,調(diào)用 get_name
方法。最后,測(cè)試代碼輸出 get_name
調(diào)用的結(jié)果。
結(jié)束語(yǔ)
如果這個(gè)動(dòng)態(tài)對(duì)象素材對(duì)您來(lái)說(shuō)理解起來(lái)有點(diǎn)難,我不會(huì)責(zé)備您。因?yàn)槲易约阂不瞬簧贂r(shí)間研究它并使用代碼才理解它并看出它的好處。
動(dòng)態(tài)對(duì)象有許多功能,但是也有相當(dāng)?shù)娘L(fēng)險(xiǎn)。首先,在剛開(kāi)始編寫(xiě)魔法方法時(shí),類的復(fù)雜性顯著增加。這些類更難理解、調(diào)試和維護(hù)。另外,因?yàn)榧砷_(kāi)發(fā)環(huán)境(IDE)變得越來(lái)越智能,所以在處理動(dòng)態(tài)類時(shí)它們也會(huì)遇到這類問(wèn)題,因?yàn)楫?dāng)它們?cè)陬惿喜檎曳椒〞r(shí)會(huì)找不到方法。
現(xiàn)在,并不是說(shuō)應(yīng)當(dāng)避免編寫(xiě)這類代碼。相反。我非常喜歡 PHP 的設(shè)計(jì)者這么有想法,把這些魔法方法包含在語(yǔ)言中,這樣我們才能編寫(xiě)這類代碼。但是重要的是,既要理解優(yōu)點(diǎn),也要理解不足。
當(dāng)然,對(duì)于應(yīng)用程序(例如數(shù)據(jù)庫(kù)訪問(wèn))來(lái)說(shuō),在這里介紹的技術(shù) —— 與廣泛流行的 Ruby on Rails 系統(tǒng)上使用的技術(shù)類似 —— 能夠極大地減少用 PHP 實(shí)現(xiàn)數(shù)據(jù)庫(kù)應(yīng)用程序所需要的時(shí)間。節(jié)約時(shí)間總不是壞事。
參考資料
學(xué)習(xí)
年輕人買(mǎi)套套的經(jīng)歷
顧客“老板套子怎么賣(mài)?”
老板“10元一個(gè)”
顧客“我先試試行么?”
老板“還試什么呀?便宜點(diǎn)9元好了。”
顧客“暈,這也叫便宜啊?”
老板“好啦8元可以了吧。”
顧客“......”
老板“不會(huì)還嫌貴吧?”
顧客“不是貴,是讓我精盡人亡啊”
老板“沒(méi)那么夸張吧,看你是小朋友,7元好了”
顧客“恩,差不多啦,可是我沒(méi)那么多錢(qián)啊。”
老板“啊?你有多少啊?”
顧客“5元。”
老板“天啊,暈死了,怎么也得再添1元啊!”
顧客“我很想添,可是我的資金有限啊”
老板“好啦,我認(rèn)倒霉,5元成交”
顧客“我不是要給你5元,我得留下2元做車(chē)”
老板“不會(huì)吧,你不會(huì)做車(chē)來(lái)這里買(mǎi)這東西吧?”
顧客“是啊我做11路來(lái)的而且還是回頭客啊”
老板“雖然以前沒(méi)看過(guò)你,不過(guò)希望你以后再來(lái),3元成交行了吧。”
顧客(臉上帶紅)“可是我還沒(méi)有對(duì)象”
老板“啊?那你買(mǎi)這干什么啊?”
顧客“沒(méi)關(guān)系,如果你再便宜1元的話我就能找到了!”
老板“¥%……¥%……%¥算你狠,2元行了吧!”
顧客“等等,這個(gè)怎么有個(gè)洞啊?”
老板“沒(méi)有洞怎么用啊!?”
顧客“怎么看起來(lái)像用過(guò)的啊?”
老板“侮辱我可以但是不要侮辱我的套子,這絕對(duì)是新的。”
顧客“哇,上面還沒(méi)干呢啊,你騙我啊!”
老板“啊!!不好意思嘿嘿……做生意嗎,你要知道我每天的門(mén)面房租金上千呢,不然我吃什么,1元可以了吧”
顧客“你這種行為嚴(yán)重的危害了我的健康并且深深的影響了我的心靈.......”
老板“啊呀!這么嚴(yán)重啊,你別生氣,我5毛賣(mài)給你行了吧。”
顧客“好,開(kāi)張發(fā)票來(lái)!”
老板暈死!
十二個(gè)避孕套
爸爸和13歲的兒子走進(jìn)屈臣氏,路經(jīng)放避孕套的貨架。兒子問(wèn)爸爸「這些一盒盒的是什
么?爸爸告訴兒子:「這些是避孕套,是用來(lái)進(jìn)行安全的性行為用的。兒子U「啊~原來(lái)這些
便是避孕套,上性教育課老師曾提及過(guò)!........[但為什么這些要一盒入里面有三個(gè)的?
爸爸:「嗯......這些是給大學(xué)生用的,星期五一個(gè),星期六一個(gè),星期日一........」
兒子:「.......那么這些一盒六個(gè)的是誰(shuí)用的?」
爸爸:「嗯........這是研究生用的,星期五兩個(gè),星期六兩個(gè),星期日兩個(gè)........」
兒子:「.......那這些呢?」
兒子拿起了一盒十二個(gè)裝的。
爸爸透了一下涼氣,凄凄道:「那是給已婚人仕用的,一月份一個(gè),二月份一個(gè),三月份一個(gè)............」
彩色的保險(xiǎn)套
有一個(gè)人想嘗試新奇的事,便跑到情趣商品店買(mǎi)彩色的保險(xiǎn)套他看到兩個(gè)彩色的套子,
一個(gè)是黑色的,一個(gè)外型像是米老鼠他決定買(mǎi)那個(gè)黑色的回家,并跟太太大戰(zhàn)了幾回合不過(guò)
那個(gè)套子并沒(méi)發(fā)生什作用,后來(lái)他太太懷孕了經(jīng)過(guò)九月之后生下小baby,再經(jīng)過(guò)6年之后孩子
長(zhǎng)大了這個(gè)小孩有一天問(wèn)他老爸:“為什么哥哥姊姊的膚色都是白的而我卻是黑的?”爸爸
回答道:“孩子,你沒(méi)長(zhǎng)得像米老鼠就該謝天謝地了”
IT避孕套
有一天軟件工業(yè)一蹶不振,軟件業(yè)三大巨頭SUN,UNIX和微軟都決定改做避孕套生意,
他們生產(chǎn)的避孕套分別命名為JAVA避孕套,X避孕套和MS避孕套。 一個(gè)使用JAVA避孕
套的顧客來(lái)到SUN公司投訴,說(shuō)戴著不合適,SUN公司回答說(shuō)要等國(guó)際標(biāo)準(zhǔn)組織(ISO)制定相
應(yīng)的標(biāo)準(zhǔn)才行,并吹牛說(shuō)那時(shí)他們生產(chǎn)的避孕套將適合每個(gè)男人,顧客只好轉(zhuǎn)而使用X避孕
套。可他發(fā)現(xiàn)等他讀完隨套附上的說(shuō)明書(shū)后,他的妻子已經(jīng)睡著了,他自己也忘了為什么
要用X避孕套。最后,他只好換用MS避孕套。出乎他意料的是,MS避孕套非常好用,他很愉
快的連續(xù)使用了好幾個(gè)月,突然發(fā)現(xiàn)他妻子懷孕了。他非常生氣氣勢(shì)洶洶的找到微軟公
司,微軟的回答是:補(bǔ)丁馬上就到!
Stripes 1.3 版本發(fā)布了.Stripes是一個(gè)視圖框架用于利用最新的Java技術(shù)來(lái)構(gòu)建Web應(yīng)用程序.
JIRA 3.6 版本發(fā)布了.JIRA是一個(gè)優(yōu)秀的問(wèn)題(or bugs,task,improvement,new feature )跟蹤及管理軟件。
Apache MyFaces 1.1.2 版本發(fā)布了.MyFaces是JavaServer Faces(JSF) Web框架 (JSR 127)的一個(gè)實(shí)現(xiàn)。JavaServer Faces Web框架是一個(gè)新的實(shí)現(xiàn)MVC模式的規(guī)范.它可以與Struts框架相媲美甚至的一些特性與觀念已經(jīng)超過(guò)了Struts.
Apple 發(fā)布了 Mac OS X Tiger 的更新包.for for Mac OS X 10.4.5 Tiger 用戶.更新包增加了對(duì)Java 2 Platform Standard Edition 5.0的可靠性和兼容性.
Apache FOP 0.92 beta 版本發(fā)布了.FOP是Apache計(jì)劃所發(fā)展的一個(gè)開(kāi)源的XSL-FO處理器項(xiàng)目,可以把Formatting Object格式的文件轉(zhuǎn)換成 可列印文件,如PDF、PostScript等格式。
ObjectWeb ASM 3.0 beta2 版本發(fā)布了.ASM是一套JAVA字節(jié)碼生成架構(gòu)。它可以動(dòng)態(tài)生成二進(jìn)制格式的stub類或其他代理類,或者在類被JAVA虛擬機(jī)裝入內(nèi)存之前,動(dòng)態(tài)修改類。
Apache WS Policy 宣布改名為 Apache Neethi.