PHP4樣本代碼:
和PHP5不一樣的是,PHP4賦值對象資源的時候是拷貝該對象,這個語法的特點本質上和值對象設計模式要求正好吻合。
然而,PHP4不能控制的屬性和方法函數(shù)在對象之外的可見性,所以實現(xiàn)一個值對象設計模式相對PHP5也有細微的差別。
假如你回想一下這本書序言中的“對象句柄”部分,它提出了三個 “規(guī)則”,當你在PHP4中使用對象去模仿PHP5中的對象句柄時,這三個規(guī)則總是適用的:
- 通過指針($obj=&new class;)來創(chuàng)建對象。
- 用指針(function funct(&$obj) param{})來傳遞對象。
- 用指針(function &some_funct() {} $returned_obj =& some_funct())來獲取一個對象。
然后,值對象設計模式卻不能使用上述三個“總是適用”的規(guī)則。只有忽視了這些規(guī)則,才能總是得到一個PHP4對象的拷貝(這相當于PHP5中的“克隆”操作,描述在http://www.php.net/manual/en/language.oop5.cloning.php)
因為PHP4可以輕松地賦值一個對象—這在PHP語言中是一個固有的行為,所以實現(xiàn)變量的不可更改就需要通過值對象通用協(xié)定來實現(xiàn)。在PHP4中,如果要使用值對象,請不要通過指針來創(chuàng)建或獲取一個對象,并且給所有需要保護以免外界修改的屬性或者方法函數(shù)命名時,都在屬性和方法函數(shù)的名字加上下劃線(_)做前綴。按照協(xié)定,變量如果具有值對象的屬性,應該使用一個下劃線來標識它的私有性。
下面是PHP4中的Dollar類:
// PHP4 class Dollar { var $_amount; function Dollar($amount=0) { $this->_amount = (float)$amount; } function getAmount() { return $this->_amount; } function add($dollar) { return new Dollar($this->_amount + $dollar->getAmount()); } function debit($dollar) { return new Dollar($this->_amount - $dollar->getAmount()); } }
下面這個實例可以說明,你不能在PHP4中限制一個屬性只能被外部更改:
function TestChangeAmount() { $d = new Dollar(5); $this->assertEqual(5, $d->getAmount()); //only possible in php4 by not respecting the _private convention $d->_amount = 10; $this->assertEqual(10, $d->getAmount()); }
再重復一次,在所有PHP4對象中,私有變量的前綴使用一個下劃線,但是你還是可以從外部來直接訪問私有屬性和方法函數(shù)。
值對象中的商業(yè)邏輯
值對象(Value Objects)不僅僅用于最小限度的訪問方法這樣的簡單的數(shù)據(jù)結構,它同樣還可以包括有價值的商業(yè)邏輯?紤]以下你如果實現(xiàn)許多人中平均分配金錢。
如果總錢數(shù)確實是可以分成整數(shù),你可以生成一組Dollar對象,而且每一個Dollar對象都擁有相同的部分。但是當總數(shù)可以整數(shù)的美元或者美分的時候,我們該怎么處理呢?
讓我們開始用一個簡單的代碼來測試一下:
// PHP5 function testDollarDivideReturnsArrayOfDivisorSize() { $full_amount = new Dollar(8); $parts = 4; $this->assertIsA( $result = $full_amount->divide($parts) ,’array’); $this->assertEqual($parts, count($result)); }
注釋 assertIsA:
assertIsA()的作用是讓你測試:一個特定的變量是否屬于一個實例化的類。當然你也可以用它來驗證變量是否屬于一些php類型:字符串、數(shù)字、數(shù)組等。
為了實現(xiàn)上述測試, Dollar::divide()方法函數(shù)的編碼如下…
public function divide($divisor) { return array_fill(0,$divisor,null); }
最好加上更多的細節(jié)。
function testDollarDrivesEquallyForExactMultiple() { $test_amount = 1.25; $parts = 4; $dollar = new Dollar($test_amount*$parts); foreach($dollar->divide($parts) as $part) { $this->assertIsA($part, ‘Dollar’); $this->assertEqual($test_amount, $part->getAmount()); } }
現(xiàn)在,應當返回存有正確數(shù)據(jù)的Dollar對象,而不是簡單的返回數(shù)量正確的數(shù)組。
實現(xiàn)這個仍然只需要一行語句:
public function divide($divisor) {
return array_fill(0,$divisor,new Dollar($this->amount / $divisor));
最后一段代碼需要解決一個除數(shù)不能把Dollar的總數(shù)均勻的除開的問題。
這是一個棘手的問題:如果存在不能均勻除開的情況,是第一部分還是最后一部分能得到一個額外的金額(便士)?怎樣獨立測試這部分的代碼?
一個方法是:明確指定代碼最后需要實現(xiàn)目標:這個數(shù)組的元素數(shù)量應該是與除數(shù)表示的數(shù)量相等的,數(shù)組的元素之間的差異不能大于0.01,并且所有部分的總數(shù)應該與被除之前的總數(shù)的值是相等的。
上面的描述通過正如下面的代碼實現(xiàn):
function testDollarDivideImmuneToRoundingErrors() { $test_amount = 7; $parts = 3; $this->assertNotEqual( round($test_amount/$parts,2), $test_amount/$parts, ’Make sure we are testing a non-trivial case %s’); $total = new Dollar($test_amount); $last_amount = false; $sum = new Dollar(0); foreach($total->divide($parts) as $part) { if ($last_amount) { $difference = abs($last_amount-$part->getAmount()); $this->assertTrue($difference <= 0.01); } $last_amount = $part->getAmount(); $sum = $sum->add($part); } $this->assertEqual($sum->getAmount(), $test_amount); }
注釋 assertNotEqual:
當你要確保兩個變量的值是不相同時,你可以用它來進行檢驗。這里面的值相同是PHP的”==”運算符進行判斷的。任何情況下當你需要確保兩個變量的值是不相同的時候,你就可以使用它。
現(xiàn)在根據(jù)上述代碼,如果來構造Dollar::divide()方法函數(shù)呢?
class Dollar { protected $amount; public function __construct($amount=0) { $this->amount = (float)$amount; } public function getAmount() { return $this->amount; } public function add($dollar) { return new Dollar($this->amount + $dollar->getAmount()); } public function debit($dollar) { return new Dollar($this->amount - $dollar->getAmount()); } public function divide($divisor) { $ret = array(); $alloc = round($this->amount / $divisor,2); $cumm_alloc = 0.0; foreach(range(1,$divisor-1) as $i) { $ret[] = new Dollar($alloc); $cumm_alloc += $alloc; } $ret[] = new Dollar(round($this->amount - $cumm_alloc,2)); return $ret; } }
這段代碼可以正常運行,但是仍然有一些問題,考慮一下如果在testDollarDivide()的開始處改變$test_amount 為 0.02; $num_parts 為 5;這樣的臨界條件,或者考慮一下當你的除數(shù)不是一個整型數(shù)字,你該怎么做?
解決上邊這些問題的方法是什么呢?還是使用測試導向的開發(fā)循環(huán)模式:增加一個需求實例,觀察可能的錯誤,編寫代碼來生成一個新的實例進行運行,還有問題存在時繼續(xù)分解。最后重復上述過程。
下文:《PHP設計模式介紹》第三章 工廠模式
本文鏈接:http://www.95time.cn/tech/program/2008/5660.asp
出處:phpchina
責任編輯:bluehearts
上一頁 php設計模式介紹之值對象模式 [4] 下一頁
◎進入論壇網(wǎng)絡編程版塊參加討論
|