激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - PHP教程 - php設(shè)計模式介紹之值對象模式

php設(shè)計模式介紹之值對象模式

2019-10-28 13:32php教程網(wǎng) PHP教程

在所有的最簡單的程序中,大多數(shù)對象都有一個標識,一個重要的商業(yè)應(yīng)用對象,例如一個Customer或者一個SKU,有一個或者更多的屬性---id,name,email地址,這樣可以把它從同一個類的其他實例區(qū)分開來。此外,對象有一個恒定的標識

例如:通常用一個對象描述一個日期、一個數(shù)字或者貨幣。日期、整數(shù)或美元的類定義是都是便于使用的、快捷、便于封裝的,并且方便進行拷貝,相互比較,甚至是創(chuàng)建。 

從表面上看,這些描述簡單的對象很容易被執(zhí)行:它們的語句非常少,在構(gòu)造類時無論是應(yīng)用于Customer還是SKU都沒有什么不同。這個想法似乎是正確的,但是所謂的"似乎正確"很容易產(chǎn)生一些bug。 

請看下面的代碼,這是一個關(guān)于以美元給員工發(fā)放工資的對象的定義和執(zhí)行操作。多數(shù)情況下,它的運行是沒有問題的。(這個類被命名為BadDollar,因為它還存在著bug)。考慮一下,看你是否能發(fā)現(xiàn)它的bug。 

// PHP5 
class BadDollar { 
protected $amount; 
public function __construct($amount=0) { 
$this->amount = (float)$amount; 

public function getAmount() { 
return $this->amount; 

public function add($dollar) { 
$this->amount += $dollar->getAmount(); 


class Work { 
protected $salary;public function __construct() { 
$this->salary = new BadDollar(200);} 
public function payDay() { 
return $this->salary; 


class Person { 
public $wallet; 

function testBadDollarWorking() { 
$job = new Work; 
$p1 = new Person; 
$p2 = new Person; 
$p1->wallet = $job->payDay(); 
$this->assertEqual(200, $p1->wallet->getAmount()); 
$p2->wallet = $job->payDay(); 
$this->assertEqual(200, $p2->wallet->getAmount()); 
$p1->wallet->add($job->payDay()); 
$this->assertEqual(400, $p1->wallet->getAmount()); 
//this is bad — actually 400 
$this->assertEqual(200, $p2->wallet->getAmount()); 
//this is really bad — actually 400 
$this->assertEqual(200, $job->payDay()->getAmount()); 

那么, bug是什么呢?如果不能上面的代碼例子中直觀地發(fā)現(xiàn)問題,這里有個提示:雇員對象$p1和對象$p2使用著同一個BadDollar對象實例。 

首先,類Work和類Person的實例已經(jīng)創(chuàng)建。那么,假設(shè)每一個雇員最初有一個空的電子錢包,雇員的電子錢包Person:wallet是通過Work::payDay()函數(shù)返回的對象資源變量賦值的,所以被設(shè)定為一個BadDollar類的對象實例。 

還記得PHP5的對象賦值處理方式嗎?因為PHP5的對象賦值的處理方式,所以$job::salary,、$p1::wallet和$p2::wallet這三個看上去不同的對象實例雖然使用著不同的“標識符”,但是事實上,它們?nèi)慷贾付ǖ酵粋€對象實例。 

因此,接下來的發(fā)放工資的操作(PayDay表示發(fā)放工資的日子,這里表示發(fā)放工資的動作),使用$job->payDay()本來僅僅是想增加$P1的工資,卻出乎意料地次給$P2也發(fā)放了。并且,這個動作還改變了工作的基本工資的額度。因此,最后兩個值的檢測報錯。 

Value Object PHP5 Unit Test 
1) Equal expectation fails because [Integer: 200] differs from [Float: 400] by 200 
in testBadDollarWorking 
in ValueObjTestCase 
2) Equal expectation fails because [Integer: 200] differs from [Float: 400] by 200 
in testBadDollarWorking 
in ValueObjTestCase 
FAILURES!!! 

問題: 

那么,你該如何為Date或Dollar這樣一些描述簡單的應(yīng)用定義一個高效的類,并且易于創(chuàng)建呢。 

解決方案: 

高效的對象應(yīng)該像PHP的整型那樣運作:如果你把同一個對象資源賦值給兩個不同的變量,然后改變其中的一個變量,另一個變量仍然不受影響。事實上,這就是Value Object模式的目標所在。 

執(zhí)行Value Object時,php4和php5是有區(qū)別的。 

正如以上你所看到的,PHP5通過new進行對象資源的賦值傳遞的是對象資源的指針就像我們在PHP4中通過指針傳遞一樣。很明顯,這是一個問題。為了解決那個問題并實現(xiàn)一個專有對象Dollar的值,我們必須使屬性$amount的對象的所有屬性的一個值在一般情況下不可變或不能改變。但是在PHP語言的沒有提供參數(shù)不可改變的功能的情況下,你完全可以結(jié)合屬性的可見性與獲得和設(shè)置方法來實現(xiàn)。 

 相反地,PHP4操作所有的對象都是遵循Value Objects對象規(guī)律的,因為PHP4的賦值操作相當于對對象做了一個拷貝。所以為了在PHP4中實現(xiàn)Value Objects設(shè)計模式你需要打破你細心地培養(yǎng)的通過指針賦值來創(chuàng)建、傳遞、提取對象的習(xí)慣。 

注:術(shù)語 不可變的(Immutable): 

在詞典中Immutable的定義是不允許或不易受影響。在編程中,這個術(shù)語表示一個一旦被設(shè)置就不能改變的值。 

PHP5 樣本代碼: 

既然我們開始用PHP5編寫代碼,讓我們優(yōu)化一個PHP5的Value Object的實例并創(chuàng)建一個較好的Dollar類定義。命名在面向?qū)ο缶幊讨蟹浅V匾x擇一個唯一的貨幣類型作為這個類的名字,說明它不被定義為可以處理多種貨幣類型的類。 

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()); 


類里面的屬性如果加上protected前綴,別的類是訪問不了的。protected(和private)拒絕通過屬性直接被訪問。 

通常,當你使用面向?qū)ο筮M行編程的時候,你經(jīng)常需要創(chuàng)建了一個“setter”函數(shù),就類似于: 

public setAmount($amount) 

  $this->amount=$amount; 

一樣,在這種情況下,雖然沒有設(shè)定函數(shù)Dollar::amount(),但在對象的實例化期時,參數(shù)Dollar::amount就已經(jīng)被賦值了。而函數(shù)Dollar::getAmount()只是提供一個訪問Dollar屬性的功能,在這里訪問的數(shù)據(jù)類型為浮點型。 

最有趣的變化是在Dollar::add()方法函數(shù)中。并不是直接改變$this->amount變量的值從而會改變已存在的Dollar對象實例,而是創(chuàng)建并返回一個新的Dollar實例。現(xiàn)在,盡管你指定當前對象給多個變量,但是每一個變量的變化都不會影響其它的變量實例。 

對于價值設(shè)計模式不變性是關(guān)鍵,任何對于一個Value Object的變量amount的改變,是通過創(chuàng)建一個新的帶有不同預(yù)期值的類的實例來完成的。上文中提高的最初那個$this->amount變量的值從未改變。 

簡單來說,在PHP5里面使用價值設(shè)計模式時,需要注意以下幾個方面: 

保護值對象的屬性,禁止被直接訪問。  
在構(gòu)造函數(shù)中就對屬性進行賦值。  
去掉任何一個會改變屬性值的方式函數(shù)(setter),否則屬性值很容易被改變。  
以上三步創(chuàng)建了一個不變的值,這個值一旦被初始化設(shè)置之后就不能被改變。當然,你也應(yīng)該提供一個查看函數(shù)或者是訪問Value Object的屬性的方法,并且可以添加一些與這個類相關(guān)的函數(shù)。值對象并不是只能用在一個簡單的架構(gòu)上,它也可以實現(xiàn)重要的商務(wù)邏輯應(yīng)用。讓我們看看下一個例子: 


詳細例子: 

讓我們在一下更加復(fù)雜的例子中查看值對象模式的功能。 

讓我們開始實現(xiàn)一個的基于PHP5中Dollar類中的一個Monopoly游戲。 

第一個類Monopoly的框架如下: 

class Monopoly { 
protected $go_amount; 
/** 
* game constructor 
* @return void 
*/ 
public function __construct() { 
$this->go_amount = new Dollar(200); 

/** 
* pay a player for passing 揋o?/span> 
* @param Player $player the player to pay 
* @return void 
*/ 
public function passGo($player) { 
$player->collect($this->go_amount); 


目前,Monopoly的功能比較簡單。構(gòu)造器創(chuàng)建一個Dollar類的實例$go_amount,設(shè)定為200,實例go_amount常常被passtGo()函數(shù)調(diào)用,它帶著一個player參數(shù),并讓對象player的函數(shù)collect為player機上200美元. 

Player類的聲明請看下面代碼,Monoplay類調(diào)用帶一個Dollar參數(shù)的Player::collect()方法。然后把Dollar的數(shù)值加到Player的現(xiàn)金余額上。另外,通過判斷Player::getBalance()方法函數(shù)返回來的余額,我們可以知道使訪問當前Player和Monopoly對象實例是否在工作中。 

class Player { 
protected $name; 
protected $savings; 
/** 
* constructor 
* set name and initial balance 
* @param string $name the players name 
* @return void 
*/ 
public function __construct($name) { 
$this->name = $name; 
$this->savings = new Dollar(1500); 

/** 
* receive a payment 
* @param Dollar $amount the amount received 
* @return void 
*/ 
public function collect($amount) { 
$this->savings = $this->savings->add($amount); 

* return player balance 
* @return float 
*/ 
public function getBalance() { 
return $this->savings->getAmount(); 


上邊已經(jīng)給出了一個Monopoly和Player類,你現(xiàn)在可以根據(jù)目前聲明的幾個類定義進行一些測試了。 

MonopolyTestCase的一個測試實例可以像下面這樣寫: 

class MonopolyTestCase extends UnitTestCase { 
function TestGame() { 
$game = new Monopoly; 
$player1 = new Player(‘Jason'); 
$this->assertEqual(1500, $player1->getBalance()); 
$game->passGo($player1); 
$this->assertEqual(1700, $player1->getBalance()); 
$game->passGo($player1); 
$this->assertEqual(1900, $player1->getBalance()); 


如果你運行MonopolyTestCase這個測試代碼,代碼的運行是沒有問題的。現(xiàn)在可以添加一些新的功能。 

另一個重要的概念是對象Monopoly中的租金支付。讓我們首先寫一個測試實例(測試引導(dǎo)開發(fā))。下面的代碼希望用來實現(xiàn)既定的目標。 

function TestRent() { 
$game = new Monopoly; 
$player1 = new Player(‘Madeline'); 
$player2 = new Player(‘Caleb'); 
$this->assertEqual(1500, $player1->getBalance()); 
$this->assertEqual(1500, $player2->getBalance()); 
$game->payRent($player1, $player2, new Dollar(26)); 
$this->assertEqual(1474, $player1->getBalance()); 
$this->assertEqual(1526, $player2->getBalance()); 

根據(jù)這個測試代碼,我們需要在Monopoly對象中增加payRent()的方法函數(shù)來實現(xiàn)一個Player對象去支付租金給另一個Player對象. 

Class Monopoly { 
// ... 
/** 
* pay rent from one player to another 
* @param Player $from the player paying rent 
* @param Player $to the player collecting rent 
* @param Dollar $rent the amount of the rent 
* @return void 
*/ 
public function payRent($from, $to, $rent) { 
$to->collect($from->pay($rent)); 


payRent()方法函數(shù)實現(xiàn)了兩個player對象之間($from和$to)的租金支付。方法函數(shù)Player::collect()已經(jīng)被定義了,但是Player::pay()必須被添加進去,以便實例$from通過pay()方法支付一定的Dollar數(shù)額$to對象中。首先我們定義Player::pay()為: 

class Player { 
// ... 
public function pay($amount) { 
$this->savings = $this->savings->add(-1 * $amount); 


但是,我們發(fā)現(xiàn)在PHP中你不能用一個數(shù)字乘以一個對象(不像其他語言,PHP不允許重載操作符,以便構(gòu)造函數(shù)進行運算)。所以,我們通過添加一個debit()方法函數(shù)實現(xiàn)Dollar對象的減的操作。 

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()); 


引入Dollar::debit()后,Player::pay()函數(shù)的操作依然是很簡單的。 

class Player { 
// ... 
/** 
* make a payment 
* @param Dollar $amount the amount to pay 
* @return Dollar the amount payed 
*/ 
public function pay($amount) { 
$this->savings = $this->savings->debit($amount); 
return $amount; 


Player::pay()方法返回支付金額的$amount對象,所以Monopoly::payRent()中的語句$to->collect($from->pay($rent))的用法是沒有問題的。這樣做的話,如果將來你添加新的“商業(yè)邏輯”用來限制一個player不能支付比他現(xiàn)有的余額還多得金額。(在這種情況下,將返回與player的賬戶余額相同的數(shù)值。同時,也可以調(diào)用一個“破產(chǎn)異常處理”來計算不足的金額,并進行相關(guān)處理。對象$to仍然從對象$from中取得$from能夠給予的金額。) 

注:術(shù)語------商業(yè)邏輯 

在一個游戲平臺的例子上提及的“商業(yè)邏輯”似乎無法理解。這里的商業(yè)的意思并不是指正常公司的商業(yè)運作,而是指因為特殊應(yīng)用領(lǐng)域需要的概念。請把它認知為 “一個直接的任務(wù)或目標”,而不是“這里面存在的商業(yè)操作”。 

所以,既然目前我們討論的是一個Monopoly,那么這里的 “商業(yè)邏輯”蘊含的意思就是針對一個游戲的規(guī)則而說的。 

PHP4樣本代碼: 

和PHP5不一樣的是,PHP4賦值對象資源的時候是拷貝該對象,這個語法的特點本質(zhì)上和值對象設(shè)計模式要求正好吻合。 

然而,PHP4不能控制的屬性和方法函數(shù)在對象之外的可見性,所以實現(xiàn)一個值對象設(shè)計模式相對PHP5也有細微的差別。 

假如你回想一下這本書序言中的“對象句柄”部分,它提出了三個 “規(guī)則”,當你在PHP4中使用對象去模仿PHP5中的對象句柄時,這三個規(guī)則總是適用的: 

通過指針($obj=&new class;)來創(chuàng)建對象。  
用指針(function funct(&$obj) param{})來傳遞對象。  
用指針(function &some_funct() {} $returned_obj =& some_funct())來獲取一個對象。 
然后,值對象設(shè)計模式卻不能使用上述三個“總是適用”的規(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é)定,變量如果具有值對象的屬性,應(yīng)該使用一個下劃線來標識它的私有性。 

下面是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()); 

再重復(fù)一次,在所有PHP4對象中,私有變量的前綴使用一個下劃線,但是你還是可以從外部來直接訪問私有屬性和方法函數(shù)。  

值對象中的商業(yè)邏輯 

值對象(Value Objects)不僅僅用于最小限度的訪問方法這樣的簡單的數(shù)據(jù)結(jié)構(gòu),它同樣還可以包括有價值的商業(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)在,應(yīng)當返回存有正確數(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ù)量應(yīng)該是與除數(shù)表示的數(shù)量相等的,數(shù)組的元素之間的差異不能大于0.01,并且所有部分的總數(shù)應(yīng)該與被除之前的總數(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ù)上述代碼,如果來構(gòu)造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ù)字,你該怎么做? 

解決上邊這些問題的方法是什么呢?還是使用測試導(dǎo)向的開發(fā)循環(huán)模式:增加一個需求實例,觀察可能的錯誤,編寫代碼來生成一個新的實例進行運行,還有問題存在時繼續(xù)分解。最后重復(fù)上述過程。 

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产日本在线播放 | 国产亚洲精品成人a | 在线成人免费视频 | 精品亚洲国产视频 | 精品国产99久久久久久宅男i | 1314成人网| h视频免费观看 | 国产韩国精品一区二区三区久久 | 免费观看又色又爽又黄的崩锅 | 成人 在线| 黄色网址在线免费 | 成人福利在线观看 | 成年人黄色片视频 | 久久久久免费精品国产小说色大师 | 91精品免费在线 | 久久久成人一区二区免费影院 | 精品国产一区二区三区四 | 男女羞羞在线观看 | 国产大片在线观看 | 欧美亚成人 | 91九色网址 | 精品一区二区三区免费毛片爱 | 精品一区二区三区在线观看视频 | 国产午夜精品一区二区三区不卡 | 国产精品久久久久久久久久久久久久久久 | 国产在线观看91一区二区三区 | 污黄视频在线播放 | 色屁屁xxxxⅹ在线视频 | 精精国产xxxx视频在线播放7 | 久久久久亚洲国产精品 | 国产一区二区免费看 | 一级黄色片武则天 | 末成年女av片一区二区 | 日韩黄色片免费看 | 精品欧美一区二区精品久久 | 国产精品视频一区二区三区四 | 久久久久久久久久久影视 | 一区二区三区四区精品 | 一级黄色免费观看 | 精品国产91久久久久 | 国产精品手机在线亚洲 |