PHP V5 新的面向對象編程特性顯著提升了這個流行語言中的功能層次。學習如何用 PHP V5 動態特性創建可以滿足需求的對象。
PHP V5 中新的面向對象編程(OOP)特性的引入顯著提升了這個編程語言的功能層次。現在不僅有了私有的、受保護的和公共的成員變量和函數 —— 就像在 Java?、 C++ 或 C# 編程語言中一樣 —— 但是還可以創建在運行時變化的對象,即動態地創建新方法和成員變量。而使用 Java、C++ 或 C# 語言是做不到這件事的。這種功能使得超級快速的應用程序開發系統(例如 Ruby on Rails)成為可能。
但是,在進入這些之前,有一點要注意:本文介紹 PHP V5 中非常高級的 OOP 特性的使用,但是這類特性不是在每個應用程序中都需要的。而且,如果不具備 OOP 的堅實基礎以及 PHP 對象語法的初步知識,這類特性將會很難理解。
動態的重要性
對象是把雙刃劍。一方面,對象是封裝數據和邏輯并創建更容易維護的系統的重大方式。但另一方面,它們會變得很繁瑣,需要許多冗余的代碼,這時可能最希望做到的就是不要犯錯。這類問題的一個示例來自數據庫訪問對象。一般來說,想用一個類代表每個數據庫表,并執行以下功能:對象從數據庫讀出數據行;允許更新字段,然后用新數據更新數據庫或刪除行。還有一種方法可以創建新的空對象,設置對象的字段,并把數據插入數據庫。
如果在數據庫中有一個表,名為 Customers,那么就應當有一個對象,名為 Customer
,它應當擁有來自表的字段,并代表一個客戶。而且 Customer
對象應當允許插入、更新或刪除數據庫中對應的記錄。現在,一切都很好,而且有也很多意義。但是,有許多代碼要編寫。如果在數據庫中有 20 個表,就需要 20 個類。
有三個解決方案可以采用。第一個解決方案就是,坐在鍵盤前,老老實實地錄入一段時間。對于小項目來說,這還可以,但是我很懶。第二個解決方案是用代碼生成器,讀取數據庫模式,并自動編寫代碼。這是個好主意,而且是另一篇文章的主題。第三個解決方案,也是我在本文中介紹的,是編寫一個類,在運行時動態地把自己塑造成指定表的字段。這個類執行起來比起特定于表的類可能有點慢 —— 但是把我從編寫大量代碼中解脫出來。這個解決方案在項目開始的時候特別有用,因為這時表和字段不斷地變化,所以跟上迅速的變化是至關重要的。
所以,如何才能編寫一個能夠彎曲 的類呢?
寫一個柔性的類
對象有兩個方面:成員變量 和方法。在編譯語言(例如 Java)中,如果想調用不存在的方法或引用不存在的成員變量,會得到編譯時錯誤。但是,在非編譯語言,例如 PHP 中,會發生什么?
在 PHP 中的方法調用是這樣工作的。首先,PHP 解釋器在類上查找方法。如果方法存在,PHP 就調用它。如果沒有,那么就調用類上的魔法方法 __call
(如果這個方法存在的話)。如果 __call
失敗,就調用父類方法,依此類推。
|
魔法方法
魔法方法是有特定名稱的方法,PHP 解釋器在腳本執行的特定點上會查找魔法方法。最常見的魔法方法就是對象創始時調用的構造函數。
|
|
__call
方法有兩個參數:被請求的方法的名稱和方法參數。如果創建的 __call
方法接受這兩個參數,執行某項功能,然后返回 TRUE,那么調用這個對象的代碼就永遠不會知道在有代碼的方法和 __call
機制處理的方法之間的區別。通過這種方式,可以創建這樣的對象,即動態地模擬擁有無數方法的情況。
除了 __call
方法,其他魔法方法 —— 包括 __get
和 __set
—— 調用它們的時候,都是因為引用了不存在的實例變量。腦子里有了這個概念之后,就可以開始編寫能夠適應任何表的動態數據庫訪問類了。
經典的數據庫訪問
先從一個簡單的數據庫模式開始。清單 1 所示的模式針對的是單一的數據表數據庫,容納圖書列表。
清單 1. MySQL 數據庫模式
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 )
);
|
請把這個模式裝入到名為 bookdb 的數據庫。
接下來,編寫一個常規的數據庫類,然后再把它修改成動態的。清單 2 顯示了圖書表的簡單的數據庫訪問類。
清單 2. 基本的數據庫訪問客戶機
<?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( );
?>
|
為了保持代碼簡單,我把類和測試代碼放在一個文件中。文件首先得到數據庫句柄,句柄保存在一個全局變量中。然后定義 Book
類,用私有成員變量代表每個字段。還包含了一套用來從數據庫裝入、插入、更新和刪除行的方法。
底部的測試代碼先刪除數據庫中的所有條目。然后,代碼插入一本書,輸出新記錄的 ID。然后,代碼把這本書裝入另一個對象并輸出書名。
清單 3 顯示了在命令行上用 PHP 解釋器運行代碼的效果。
清單 3. 在命令行運行代碼
% php db1.php
New book id = 25
Title = PHP Hacks
%
|
不需要看太多,就已經得到重點了。Book
對象代表圖書數據表中的行。通過使用上面的字段和方法,可以創建新行、更新行和刪除行。
初識動態
下一步是讓類變得稍微動態一些:動態地為每個字段創建 get_
和 set_
方法。清單 4 顯示了更新后的代碼。
清單 4. 動態 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 );
}
}
..
|
要做這個變化,需要做兩件事。首先,必須把字段從單個實例變量修改成字段和值組合構成的散列表。然后必須添加一個 __call
方法,它只查看方法名稱,看方法是 set_
還是 get_
方法,然后在散列表中設置適當的字段。
注意,load
方法通過調用 set_title
、set_author
和 set_publisher
方法 —— 實際上都不存在 —— 來實際使用 __call
方法。
走向完全動態
刪除 get_
和 set_
方法只是一個起點。要創建完全動態的數據庫對象,必須向類提供表和字段的名稱,還不能有硬編碼的引用。清單 5 顯示了這個變化。
清單 5. 完全動態的數據庫對象類
<?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
。然后,把構造函數修改成接受表的名稱和表中字段的名稱。之后,大多數變化發生在類的方法中,過去使用一些硬編碼結構化查詢語言(SQL),現在則必須用表和字段的名稱動態地創建 SQL 字符串。
代碼的惟一假設就是只有一個主鍵字段,而且這個字段的名稱是表名加上 _id
。所以,在 book
表這個示例中,有一個主鍵字段叫做 book_id
。主鍵的命名標準可能不同;如果這樣,需要修改代碼以符合標準。
這個類比最初的 Book
類復雜得多。但是,從類的客戶的角度來看,這個類用起來仍很簡單。也就是說,我認為這個類能更簡單。具體來說,我不愿意每次創建圖書的時候都要指定表和字段的名稱。如果我四處拷貝和粘貼這個代碼,然后修改了 book 表的字段結構,那么我可能就麻煩了。在清單 6 中,通過創建一個繼承自 DBObject
的簡單 Book
類,我解決了這個問題。
清單 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( );
?>
|
現在,Book
類真的是簡單了。而且 Book
類的客戶也不再需要知道表或字段的名稱了。
改進的空間
對這個動態類我想做的最后一個改進,是用成員變量訪問字段,而不是用笨重的 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( );
?>
|
底部的測試代碼只演示了這個語法干凈了多少。要得到圖書的書名,只需得到 title
成員變量。這個變量會調用對象的 __get
方法,在散列表中查找 title
條目并返回。
現在就得到了單個動態的數據庫訪問類,它能夠讓自己適應到數據庫中的任何表。
動態類的更多用途
編寫動態類不僅限于數據庫訪問。請看清單 8 中的 Customer
對象這個例子。
清單 8. 簡單的 Customer 對象
<?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" );
?>
|
這個對象足夠簡單。但是如果我想在每次檢索或設置客戶名稱時都記錄日志,會發生什么呢?我可以把這個對象包裝在一個動態日志對象內,這個對象看起來像 Customer
對象,但是會把 get
或 set
操作的通知發送給日志。清單 9 顯示了這類包裝器對象。
清單 9. 動態包裝器對象
<?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" );
?>
|
調用日志版本的 Customer
的代碼看起來與前面相同,但是這時,對 Customer
對象的任何訪問都被記入日志。清單 10 顯示了運行這個日志版代碼時輸出的日志。
清單 10. 運行日志版對象
% php log2.php
set_name( Jack )
get_name( )
name = Jack
%
|
在這里,日志輸出表明用參數 Jack
調用了set_name
方法。然后,調用 get_name
方法。最后,測試代碼輸出 get_name
調用的結果。
結束語
如果這個動態對象素材對您來說理解起來有點難,我不會責備您。因為我自己也花了不少時間研究它并使用代碼才理解它并看出它的好處。
動態對象有許多功能,但是也有相當的風險。首先,在剛開始編寫魔法方法時,類的復雜性顯著增加。這些類更難理解、調試和維護。另外,因為集成開發環境(IDE)變得越來越智能,所以在處理動態類時它們也會遇到這類問題,因為當它們在類上查找方法時會找不到方法。
現在,并不是說應當避免編寫這類代碼。相反。我非常喜歡 PHP 的設計者這么有想法,把這些魔法方法包含在語言中,這樣我們才能編寫這類代碼。但是重要的是,既要理解優點,也要理解不足。
當然,對于應用程序(例如數據庫訪問)來說,在這里介紹的技術 —— 與廣泛流行的 Ruby on Rails 系統上使用的技術類似 —— 能夠極大地減少用 PHP 實現數據庫應用程序所需要的時間。節約時間總不是壞事。
參考資料
學習
年輕人買套套的經歷
顧客“老板套子怎么賣?”
老板“10元一個”
顧客“我先試試行么?”
老板“還試什么呀?便宜點9元好了。”
顧客“暈,這也叫便宜啊?”
老板“好啦8元可以了吧。”
顧客“......”
老板“不會還嫌貴吧?”
顧客“不是貴,是讓我精盡人亡啊”
老板“沒那么夸張吧,看你是小朋友,7元好了”
顧客“恩,差不多啦,可是我沒那么多錢啊。”
老板“啊?你有多少啊?”
顧客“5元。”
老板“天啊,暈死了,怎么也得再添1元啊!”
顧客“我很想添,可是我的資金有限啊”
老板“好啦,我認倒霉,5元成交”
顧客“我不是要給你5元,我得留下2元做車”
老板“不會吧,你不會做車來這里買這東西吧?”
顧客“是啊我做11路來的而且還是回頭客啊”
老板“雖然以前沒看過你,不過希望你以后再來,3元成交行了吧。”
顧客(臉上帶紅)“可是我還沒有對象”
老板“啊?那你買這干什么啊?”
顧客“沒關系,如果你再便宜1元的話我就能找到了!”
老板“¥%……¥%……%¥算你狠,2元行了吧!”
顧客“等等,這個怎么有個洞啊?”
老板“沒有洞怎么用啊!?”
顧客“怎么看起來像用過的啊?”
老板“侮辱我可以但是不要侮辱我的套子,這絕對是新的。”
顧客“哇,上面還沒干呢啊,你騙我啊!”
老板“啊!!不好意思嘿嘿……做生意嗎,你要知道我每天的門面房租金上千呢,不然我吃什么,1元可以了吧”
顧客“你這種行為嚴重的危害了我的健康并且深深的影響了我的心靈.......”
老板“啊呀!這么嚴重啊,你別生氣,我5毛賣給你行了吧。”
顧客“好,開張發票來!”
老板暈死!
十二個避孕套
爸爸和13歲的兒子走進屈臣氏,路經放避孕套的貨架。兒子問爸爸「這些一盒盒的是什
么?爸爸告訴兒子:「這些是避孕套,是用來進行安全的性行為用的。兒子U「啊~原來這些
便是避孕套,上性教育課老師曾提及過!........[但為什么這些要一盒入里面有三個的?
爸爸:「嗯......這些是給大學生用的,星期五一個,星期六一個,星期日一........」
兒子:「.......那么這些一盒六個的是誰用的?」
爸爸:「嗯........這是研究生用的,星期五兩個,星期六兩個,星期日兩個........」
兒子:「.......那這些呢?」
兒子拿起了一盒十二個裝的。
爸爸透了一下涼氣,凄凄道:「那是給已婚人仕用的,一月份一個,二月份一個,三月份一個............」
彩色的保險套
有一個人想嘗試新奇的事,便跑到情趣商品店買彩色的保險套他看到兩個彩色的套子,
一個是黑色的,一個外型像是米老鼠他決定買那個黑色的回家,并跟太太大戰了幾回合不過
那個套子并沒發生什作用,后來他太太懷孕了經過九月之后生下小baby,再經過6年之后孩子
長大了這個小孩有一天問他老爸:“為什么哥哥姊姊的膚色都是白的而我卻是黑的?”爸爸
回答道:“孩子,你沒長得像米老鼠就該謝天謝地了”
IT避孕套
有一天軟件工業一蹶不振,軟件業三大巨頭SUN,UNIX和微軟都決定改做避孕套生意,
他們生產的避孕套分別命名為JAVA避孕套,X避孕套和MS避孕套。 一個使用JAVA避孕
套的顧客來到SUN公司投訴,說戴著不合適,SUN公司回答說要等國際標準組織(ISO)制定相
應的標準才行,并吹牛說那時他們生產的避孕套將適合每個男人,顧客只好轉而使用X避孕
套。可他發現等他讀完隨套附上的說明書后,他的妻子已經睡著了,他自己也忘了為什么
要用X避孕套。最后,他只好換用MS避孕套。出乎他意料的是,MS避孕套非常好用,他很愉
快的連續使用了好幾個月,突然發現他妻子懷孕了。他非常生氣氣勢洶洶的找到微軟公
司,微軟的回答是:補丁馬上就到!
Stripes 1.3 版本發布了.Stripes是一個視圖框架用于利用最新的Java技術來構建Web應用程序.
Apache MyFaces 1.1.2 版本發布了.MyFaces是JavaServer Faces(JSF) Web框架 (JSR 127)的一個實現。JavaServer Faces Web框架是一個新的實現MVC模式的規范.它可以與Struts框架相媲美甚至的一些特性與觀念已經超過了Struts.
Apple 發布了 Mac OS X Tiger 的更新包.for for Mac OS X 10.4.5 Tiger 用戶.更新包增加了對Java 2 Platform Standard Edition 5.0的可靠性和兼容性.
Apache FOP 0.92 beta 版本發布了.FOP是Apache計劃所發展的一個開源的XSL-FO處理器項目,可以把Formatting Object格式的文件轉換成 可列印文件,如PDF、PostScript等格式。
ObjectWeb ASM 3.0 beta2 版本發布了.ASM是一套JAVA字節碼生成架構。它可以動態生成二進制格式的stub類或其他代理類,或者在類被JAVA虛擬機裝入內存之前,動態修改類。
Apache WS Policy 宣布改名為 Apache Neethi.