PHP4 sample code:
Unlike PHP5, PHP4 copies the object when assigning an object resource. The characteristics of this syntax are essentially consistent with the value object design pattern requirements.
However, PHP4 cannot control the visibility of properties and method functions outside the object, so implementing a value object design pattern also has slight differences compared to PHP5.
If you recall the "Object Handle" section in the preface of this book, it proposes three "rules". When you use objects in PHP4 to imitate object handles in PHP5, these three rules always apply:
Create an object through a pointer ($obj=&new class;).
Use a pointer (function function(&$obj) param{}) to pass the object.
Use a pointer (function &some_funct() {} $returned_obj =& some_funct()) to get an object.
Then, the value object design pattern cannot use the above three "always applicable" rules. Only by neglecting these rules can you always get a copy of a PHP4 object (this is equivalent to the "clone" operation in PHP5, described in /manual/en/language.)
Because PHP4 can easily assign an object—this is an inherent behavior in the PHP language, implementing the invariant variables needs to be implemented through the value object general contract. In PHP4, if you want to use a value object, please do not create or obtain an object through a pointer, and when naming all attributes or method functions that need protection from external modification, you should add an underscore (_) to the name of the attribute and method functions. By agreement, if a variable has properties of a value object, it should use an underscore to identify its privacy.
Here is the Dollar class in PHP4:
// 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());
}
}
The following example shows that you cannot restrict a property in PHP4 to be changed externally:
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());
}
Repeat again, in all PHP4 objects, the private variable prefix uses an underscore, but you can still access private properties and method functions directly from the outside.
Business logic in value objects
Value Objects are not only used for simple data structures such as minimal access methods, but can also include valuable business logic. Consider the following if you achieve equal distribution of money among many people.
If the total amount of money can be divided into integers, you can generate a set of Dollar objects, and each Dollar object has the same part. But when the total number can be an integer of dollars or cents, what should we do?
Let's start with a simple code to test it:
// PHP5
function testDollarDivideReturnsArrayOfDivisorSize() {
$full_amount = new Dollar(8);
$parts = 4;
$this->assertIsA(
$result = $full_amount->divide($parts)
,'array');
$this->assertEqual($parts, count($result));
}
Comment assertIsA:
The function of assertIsA() is to let you test whether a specific variable belongs to an instantiated class. Of course, you can also use it to verify whether the variable belongs to some php types: strings, numbers, arrays, etc.
In order to implement the above test, the encoding of the Dollar::divide() method function is as follows...
public function divide($divisor) {
return array_fill(0,$divisor,null);
}
It's better to add more details.
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());
}
}
Now, the Dollar object with the correct data should be returned instead of simply returning the correct number of arrays.
To implement this, only one line of statements is required:
public function divide($divisor) {
return array_fill(0,$divisor,new Dollar($this->amount / $divisor));
The last piece of code needs to solve the problem that a divisor cannot evenly divide the total number of Dollar.
This is a tricky question: if there is a situation where it cannot be evenly removed, is the first or the last part able to get an extra amount (pence)? How to test this part of the code independently?
One method is: specify the code to achieve the goal in the end: the number of elements in this array should be equal to the number represented by the divisor, the difference between elements in the array cannot be greater than 0.01, and the total number of all parts should be equal to the value of the total number before being divided.
The above description is implemented by the following code:
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);
}
Comment assertNotEqual:
When you want to make sure that the values of the two variables are different, you can use it to test it. The same value here is judged by the PHP "==" operator. In any case, you can use it when you need to make sure that the values of the two variables are different.
Now based on the above code, what if we construct the Dollar::divide() method function?
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;
}
}
This code works fine, but there are still some problems. Consider the critical condition such as "change $test_amount" to 0.02 at the beginning of testDollarDivide(); $num_parts is 5; or consider how you do it when your divisor is not an integer?
What is the solution to the above problems? Or use the test-oriented development loop mode: add a requirement instance, observe possible errors, write code to generate a new instance for running, and continue to decompose when there is still a problem. Finally, repeat the above process.