In all the simplest programs, most objects have an identifier. an important commercial application object, such as a Customer or a SKU, has one or more attributes ---
In all the simplest programs, most objects have an identifier. an important commercial application object, such as a Customer or a SKU, has one or more attributes-id, name, email address, which can be distinguished from other instances of the same class. In addition, an object has A constant identifier: it is A unique identifier throughout the application. for programmers, "customer A" is "customer A" anywhere ", and as long as your program continues to run, "customer A" is still "customer ". However, an object does not need an identifier. Some objects only describe the attributes of other objects.
For example, an object is usually used to describe a date, number, or currency. Date, integer, or dollar class definitions are easy to use, fast, and easy to encapsulate, and easy to copy, compare, or even create.
On the surface, objects with simple descriptions can be easily executed: they have very few statements, and they are no different in constructing classes, whether applied to Customer or SKU. This idea seems to be correct, but the so-called "seemingly correct" can easily produce some bugs.
Please refer to the following code. this is a definition and execution of the object that grants wages to employees in USD. In most cases, it runs normally. (This class is named BadDollar because it still has bugs ). Consider whether you can discover its bugs.
// 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 ());
}
So what is a bug? If you cannot find the problem in the preceding code example, the following message is displayed: The Employee object $ p1 and object $ p2 use the same BadDollar object instance.
First, instances of class Work and class Person have been created. Assume that each employee initially has an empty e-wallet. the employee's e-wallet Person: wallet is assigned a value through the object resource variable returned by the Work: payDay () function, so it is set as an object instance of the BadDollar class.
Do you still remember the PHP 5 object assignment processing method? Because of the processing method of PHP5 object assignment, $ job: salary, $ p1: wallet and $ p2 :: although the three different object instances of wallet use different "identifiers", they all specify the same object instance.
Therefore, for the next pay-as-you-go operation (PayDay indicates the pay-as-you-go Day, which indicates the pay-as-you-go action), use $ job-> payDay () I only wanted to increase the salary of $ P1, but unexpectedly paid $ P2. In addition, this operation also changes the amount of basic salary for the job. Therefore, an error is reported when detecting the last two values.
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 !!!
Problem:
Then, how do you define an efficient class for some simple applications such as Date or Dollar and it is easy to create.
Solution:
Efficient objects should work like PHP integers: if you assign the same object resource to two different variables and change one of them, the other variables will remain unaffected. In fact, this is the target of the Value Object mode.
When executing a Value Object, php4 and php5 are different.
As you can see above, PHP5 assigns a value to an object resource through new, passing a pointer to an object resource, just like passing a pointer in PHP4. Obviously, this is a problem. To solve this problem and implement the Dollar value of a proprietary object, we must make one value of all attributes of the object of attribute $ amount unchangeable or unchangeable in general. However, if the PHP language does not provide the unchangeable parameter function, you can fully combine the visibility and acquisition and setting methods of attributes.
On the contrary, all Objects operated by PHP4 follow the Value Objects object rule, because the Value assignment operation of PHP4 is equivalent to copying the object. Therefore, to implement the Value Objects design mode in PHP4, you need to break the habit of creating, passing, and extracting Objects through pointer assignment carefully.
Note: Immutable ):
The definition of Immutable in the dictionary is not allowed or is not easily affected. In programming, this term indicates a value that cannot be changed once set.
PHP5 sample code:
Since we started to write code with PHP5, let's optimize the Value Object instance of PHP5 and create a good Dollar class definition. Naming is very important in object-oriented programming. selecting a unique currency type as the name of this class means that it is not defined as a class that can process multiple currency types.
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 ());
}
}
If the attribute in the class is prefixed with protected, other classes cannot be accessed. Protected (and private) rejects direct access through attributes.
Generally, when you use object-oriented programming, you often need to create a "setter" function, which is similar:
Public setAmount ($ amount)
{
$ This-> amount = $ amount;
}
In this case, although the Dollar: amount () function is not set, the Dollar: amount parameter is assigned a value during object instantiation. The Dollar: getAmount () function only provides a function to access the Dollar attribute. The data type accessed here is the floating point type.
The most interesting change is in the Dollar: add () method function. Instead of directly changing the value of $ this-> amount variable, the existing Dollar object instance is changed. Instead, a new Dollar instance is created and returned. Now, although you specify the current object to multiple variables, every variable change will not affect other variable instances.
The immutability of the Value design pattern is critical. any change to the amount variable of a Value Object is accomplished by creating a new instance of a class with different expected values. The value of the first $ this-> amount variable raised above has never changed.
To put it simply, pay attention to the following aspects when using the value design model in PHP5:
- The attribute of the protected value object cannot be accessed directly.
- Assign a value to the attribute in the constructor.
- Remove any method function (setter) that will change the attribute value. Otherwise, the attribute value is easily changed.
A constant value is created in the preceding three steps. Once the value is initialized and set, it cannot be changed. Of course, you should also provide a method to view functions or access the attributes of Value Object, and you can add some functions related to this class. Value objects can not only be used in a simple architecture, but also implement important business logic applications. Let's take a look at the next example:
Example:
Let's look at the Value Object mode function in a more complex example.
Let's start implementing a Monopoly game based on the Dollar class in PHP5.
The first Monopoly framework is as follows:
Class Monopoly {
Protected $ go_amount;
/**
* Game constructor
* @ Return void
*/
Public function _ construct (){
$ This-> go_amount = new Dollar (200 );
}
/**
* Pay a player for passing into o? /Span>
* @ Param Player $ player the player to pay
* @ Return void
*/
Public function passGo ($ player ){
$ Player-> collect ($ this-> go_amount );
}
}
Currently, Monopoly has simple functions. The constructor creates an instance of the Dollar class $ go_amount, which is set to 200. the instance go_amount is often called by the passtGo () function with a player parameter, and set the object player function collect to $200 on the player machine.
For the declaration of the Player class, see the following code. the Monoplay class calls the Player: collect () method with a Dollar parameter. Then add the Dollar value to the Player's cash balance. In addition, by judging the balance returned by the Player: getBalance () method function, we can know whether the access to the current Player and Monopoly object instance are working.
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 has ed
* @ Return void
*/
Public function collect ($ amount ){
$ This-> savings = $ this-> savings-> add ($ amount );
}
* Return player balance
* @ Return float
*/
Public function getBalance (){
Return $ this-> savings-> getAmount ();
}
}
A Monopoly and Player class has been provided above. you can now perform some tests based on several declared class definitions.
A test instance of MonopolyTestCase can be written as follows:
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 ());
}
}
If you run the MonopolyTestCase test code, the code runs normally. Now you can add some new features.
Another important concept is rent payment in Monopoly. Let's first write a test instance (test-guided development ). The following code is intended to achieve the set goal.
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 ());
}
According to this test code, we need to add the payRent () method function to the Monopoly object to implement a Player object to pay the rent to another Player object.
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 ));
}
}
The payRent () method function implements rent payment between two player objects ($ from and $. The method function Player: collect () has been defined, but Player: pay () must be added so that the instance $ from passes pay () method to pay a certain Dollar amount $ to object. First, we define Player: pay ():
Class Player {
//...
Public function pay ($ amount ){
$ This-> savings = $ this-> savings-> add (-1 * $ amount );
}
}
However, we found that in PHP, you cannot multiply a number by an object (unlike other languages, PHP does not allow overload operators for constructor operations ). Therefore, we add a debit () method function to reduce the Dollar object.
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 ());
}
}
After Dollar: debit () is introduced, the operations of the Player: pay () function are still very simple.
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 () method returns the $ amount object of the payment amount, so the statement $ to-> collect ($ from-> pay ($ rent) in Monopoly: payRent )) the usage is correct. In this case, if you add a new "business logic" in the future to limit a player to be unable to pay more than his existing balance. (In this case, the same value as the player account balance is returned. At the same time, you can also call a "bankruptcy exception handling" to calculate the amount of the insufficient amount and handle it accordingly. The $ to object still obtains the amount $ from the object $ from .)
Note: term ------ business logic
The commercial logic mentioned in the example of a game platform does not seem to be understandable. Business here does not mean the business operation of a normal company, but the concept required by a special application field. Recognize it as a direct task or goal, rather than a commercial operation ".
Therefore, since we are currently talking about Monopoly, the commercial logic here refers to the rules for a game.
PHP4 sample code:
Unlike PHP5, PHP4 copies the object when assigning values to object resources. this syntax essentially matches the value object design pattern requirements.
However, PHP4 does not control the visibility of attributes and method functions beyond the object. Therefore, the design pattern of implementing a value object is slightly different from that of PHP5.
If you think back to the "object handle" section in the preface of this book, it proposes three "rules". when you use an object in PHP4 to imitate the object handle in PHP5, these three rules always apply:
- Create an object using a pointer ($ obj = & new class.
- Use the pointer (function funct (& $ obj) param {}) to pass the object.
- Use the pointer (function & some_funct () {}$ returned_obj = & some_funct () to obtain an object.
Then, the design mode of the value object cannot use the above three "always applicable" rules. Only by ignoring these rules can we always get a copy of a PHP4 object (which is equivalent to the "clone" operation in PHP5, described in the http://www.php.net/manual/en/language.oop5.cloning.php)
Because PHP4 can easily assign values to an object-this is an inherent behavior in the PHP language, it is necessary to implement the unchangeable variable through the universal protocol of value objects. In PHP4, if you want to use a value object, do not use pointers to create or obtain an object, and give all attributes or method function names that need to be protected against external modification, prefix the attribute and method function name with an underscore. According to the protocol, if a variable has a property of a value object, it should be identified by an underscore.
The following is the Dollar class in PHP4:
The following example shows that you cannot restrict an attribute from being changed externally in 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 ());
}
Repeat it again. in all PHP4 objects, the prefix of private variables uses an underscore, but you can still directly access private attributes and method functions from the outside.
Business logic in value objects
Value Objects is not only used for a simple data structure such as a minimal access method, it can also include valuable business logic. Consider the following if you achieve the average distribution of money among many people.
If the total amount of money is indeed an integer, you can generate a group of Dollar objects, and each Dollar object has the same part. But when the total number can be an integer of USD or cents, what should we do?
Let's start a Test with a simple code:
// PHP5
Function testDollarDivideReturnsArrayOfDivisorSize (){
$ Full_amount = new Dollar (8 );
$ Parts = 4;
$ This-> assertIsA (
$ Result = $ full_amount-> divide ($ parts)
, 'Array ');
$ This-> assertEqual ($ parts, count ($ result ));
}
Note assertIsA:
AssertIsA () is used to 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 type: string, number, array, etc.
To implement the above test, the Dollar: divide () method function is encoded as follows...
Public function divide ($ divisor ){
Return array_fill (0, $ divisor, null );
}
It is best 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, rather than simply returning an array with the correct number.
To achieve this, only one line of statements is required:
The last code segment solves the problem that the divisor cannot evenly split the total number of Dollar values.
This is a tricky problem: if there is a problem that the split may not be evenly distributed, will the first part or the last part be able to get an extra amount (Penny )? How to test the code independently?
One method is to explicitly specify the final implementation goal of the code: the number of elements in the array should be equal to the number indicated 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 the division.
The above description is implemented through 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 );
}
Note assertNotEqual:
When you want to ensure that the values of two variables are different, you can use them for testing. The same value is determined by the "=" operator of PHP. You can use it whenever you need to ensure that the values of the two variables are different.
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 can run normally, but there are still some problems. Consider changing $ test_amount to 0.02 at the beginning of testDollarDivide (); $ num_parts to 5; such a critical condition, or, if your divisor is not an integer, what should you do?
What are the solutions to these problems? Still use the test-oriented development cycle mode: add a demand instance, observe possible errors, write code to generate a new instance for running, and continue to break down when there are problems. Repeat the above process.
Public function divide ($ divisor ){
Return array_fill (0, $ divisor, new Dollar ($ this-> amount/$ divisor ));
// 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 ());
}
}