Software design is certainly one of the most discussed and most difficult activities in software engineering. Designing correct and complete architecture before development is virtually impossible. The real design emerges only when you implement a certain feature; reasoning in micro is easier than reasoning in macro, and requirements can change any time during the developing phase. For this reason, we will discuss emergent design, which means software design that emerges during development. This process entails renegotiating class responsibilities, properties, behaviors, and interactions.
In short, design must evolve along with application. The techniques we'll see in this chapter help us to develop a simple and correct design, and, using them, we can design today's code and not try to predict a design that will serve us tomorrow. Automated tests will support us in our daily refactoring, ensuring the value of the features already developed and helping to design new ones.
In this chapter we'll see a collection of some refactoring methods created by Martin Fowler [FOW01]. For each method we'll see the motivation and proper situation for its use, the mechanism that will explain how we can apply a method to our existing code, and some examples showing how a method works in the real world.
Problem: “A class method is used by more methods of another class than the class on which it is defined.”
Solution: “Create a new method with the same body in the class it uses most. Either turn the old method into a simple delegation, or remove it altogether.”
The “move method” technique is probably the most famous and most useful refactoring of the moving refactoring methods. It is concerned with properly distributing the responsibilities between the classes, responsibilities that are not always easy to identify in the first implementation. The move method helps us to balance these responsibilities between classes, simplifying them and making them easier to manage.
When you move an attribute from one class to another we must also move the methods that use this attribute. It is not always easy to understand if we have to move a method. When we're not sure, try first to move other methods and delay the decision. Let us trust our instincts—if we are not sure, after all, we can always move it back again.
We must refactor the getActiveInterest()
method in the BankAccount
class.
class BankAccount
{
private $balance = 0;
private $active_interest = 0.01;
private $type;
public function __construct(BankAccountType $type)
{
$this->type = $type;
}
public function getBalance()
{
return $this->balance;
}
public function deposits($money_amount)
{
$this->balance += $money_amount;
$this->calculateBalance();
}
public function getActiveInterest()
{
if ($this->type->isBusiness())
{
$interest_constant = 5;
$active_interest = $this->active_interest * $interest_constant;
}
return $active_interest;
}
public function calculateBalance()
{
if ($this->balance > 0)
{
$this->balance += $this->balance * $this->getActiveInterest();
}
}
}
We need to introduce other business account types with different interest constants. The method getActiveInterest()
will not let you change the constant interest, which is hard-coded inside and assigned to the $interest_constant variable. What we should do is move the getActiveInterest()
method in the BankAccountType
class, so we could add more BankAccountType
classes with different constant interests. First we need to identify which attribute must stay in our class and which can be moved in the BankAccountType
class. The $active_interest attribute must stay in the BankAccount
class; others can be moved in the BankAccountType
class.
First, we write a unit test for the getActiveInterest()
method of the BankAccount
class.
class BankAccountTest extends PHPUnit_Framework_TestCase
{
public function testgetActiveInterest()
{
$bank_account_type = new BankAccountType();
$bank_account_type->setBusiness(true);
$account = new BankAccount($bank_account_type);
$account->deposits(100);
$this->assertEquals(105, $account->getBalance());
}
}
We run a test and all tests run.
# phpunit BankAccountTest.php
PHPUnit 3.4.1 by Sebastian Bergmann.
.
Time: 0 seconds
OK (1 test, 1 assertion)
Then we can copy the getActiveInterest()
method from the BankAccount
class to the BankAccountType
class.
class BankAccountType
{
...
public function getActiveInterest()
{
if ($this->type->isBusiness())
{
$interest_constant = 5;
$this->active_interest = $this->active_interest * $interest_constant;
}
return $this->active_interest;
}
...
}
Remove the $type property, because it doesn't exist any more in the BankAccountType
class, being itself the instance of the $type attribute in the BankAccount
class. Then we pass the $active_interest
property as a parameter to the method and we change the call into the method from property name to variable name.
class BankAccountType
{
...
public function getActiveInterest($active_interest)
{
if ($this->isBusiness())
{
$interest_constant = 5;
$active_interest = $active_interest * $interest_constant;
}
return $active_interest;
}
...
}
The method name does not explain what the method does; in fact, the method doesn't return the active interest, but it modifies them according to a constant. So rename the method calcActiveInterest()
.
class BankAccountType
{
...
public function calcActiveInterest($active_interest)
{
if ($this->isBusiness())
{
$interest_constant = 5;
$active_interest = $active_interest * $interest_constant;
}
return $active_interest;
}
...
}
With this transaction, we have moved the getActiveInterest()
method in the BankAccountType
class now responsible for choosing constant interest. The next step is to remove the code from the getActiveInterest()
method of the BankAccount
class and add a direct call to the calcActiveInterest()
method of the BankAccountType
class, passing the $active_interest property as a parameter.
class BankAccount
{
...
public function getActiveInterest()
{
return $this->type->calcActiveInterest($this->active_interest);
}
...
}
Ok, much better. At this point, we can run the BankAccountTest test again to see if something went wrong.
We might also remove the getActiveInterest() method, which has become only a proxy method. Before deleting it, we have to replace all calls to the proxy method with the method call of the BankAccountType
object. In our case the only method that calls getActiveInterest()
is the calculateBalance()
method. Replacing the call, we get the following result:
<?php
class BankAccount
{
...
public function calculateBalance()
{
if ($this->balance > 0)
{
$this->balance += $this->balance * $this->type->calcActiveInterest($this->active_interest);
}
}
...
}
?>
We run our test for the last time and we see that all tests run.
# phpunit BankAccountTest.php
PHPUnit 3.4.1 by Sebastian Bergmann.
.
Time: 0 seconds
OK (1 test, 1 assertion)
Now we can remove the entire method getActiveInterest()
, which is now useless.
Problem: “A class property describes a feature of another class instead of a feature of the class where it is declared.”
Solution: “Create a new field in the right class, and change all the clients' calls from the old class property to the new class property.”
Move field is the name given to this refactoring method by Martin Fowler. In PHP the right name of the field class is property. So we will always call field as property (http://www.php.net/manual/en/language.oop5.basic.php
).
The move property is one of the best refactoring methods of moving features between object categories, because it is very important to characterize the class in the right way. While we're designing or implementing a class, often we incorrectly declare properties, introducing some properties that don't describe the class where they are declared. Often they describe other connected classes. Other times it may be that a property is correctly defined in its class today, but tomorrow a new feature will arise and the property will no longer describe the class correctly.
We absolutely need to move these properties between classes. One of the most important practices of the object-oriented programming paradigm is defining simple classes where all their properties are always properties of the class. If we have a property that never describes a class feature or a property that only sometimes describes a class feature, this is wrong; we need to move this attribute to a better place.
We need to move $active_interest from the BankAccount
class to the BankAccountType
class.
class BankAccount
{
...
private $active_interest = 0.01;
...
public function calculateActiveInterests()
{
if ($this->balance > 0)
{
$this->balance += $this->balance * $this->type->calcActiveInterest($this->active_interest);
}
}
}
We need it because we want to add a new feature: an active interest that is dependent on bank account type. $active_interest isn't an attribute of the BankAccountType
class, so we can't change it for a different bank account type. We could do it only by introducing a lot of “ifs,” but we know that “if” is undesirable because it makes our code difficult to maintain, read, and manage.
We want the $active_interest property to depend on the BankAccountType
class, which means that the $active_interest attribute describes the BankAccountType
class, not the BankAccount
class. So we have to move this field from the BankAccount
class to the BankAccountType
class.
To do this, first of all, we need to declare the same attribute in the target class with all accessory methods.
class BankAccountType
{
...
private $active_interest = 0.01;
...
public function setActiveInterest($active_interest)
{
$this->active_interest = $active_interest;
}
public function getActiveInterest()
{
return $this->active_interest;
}
}
Then we have to change the BankAccount
class code where there are $active_interest property calls with the getActiveInterest()
method of BankAccountType
and remove its declaration.
class BankAccount
{
public function calculateActiveInterests()
{
if ($this->balance > 0)
{
$this->balance += $this->balance * $this->type->calcActiveInterest($this->type->
getActiveInterest());
}
}
}
If we have accessory methods for the class property we are moving, we need to modify them to self-encapsulate the new call.
class BankAccount
{
...
private $active_interest = 0.01;
...
public function calculateActiveInterests()
{
if ($this->balance > 0)
{
$this->balance += $this->balance * $this->type->calcActiveInterest($this->
getActiveInterest());
}
}
public function setActiveInterest($value)
{
return $this->active_interest = $value;
}
public function getActiveInterest()
{
return $this->active_interest;
}
}
In this case we use accessory methods to access the field we want to move. We have to change the $active_interest call into accessory methods with the new call to target class accessory methods.
class BankAccount
{
public function calculateActiveInterests()
{
if ($this->balance > 0)
{
$this->balance += $this->balance * $this->type->calcActiveInterest($this->
getActiveInterest());
}
}
public function setActiveInterest($value)
{
return $this->type->setActiveInterest($value);
}
public function getActiveInterest()
{
return $this->type->getActiveInterest();
}
}
This step is small, so that we don't go too fast with our refactoring. After this step we can also remove the source accessory methods and change all the calls to these methods with the target accessory methods.
Problem: “We have one class that has responsibilities for more classes than one.”
Solution: “Create a new class, and move the properties and methods that don't belong to the origin class into the new class.”
One of the best object programming practices is to have small classes that are highly empowered for a single feature. This is because maintaining small classes with small methods and a few smaller properties is much easier than maintaining very large classes with multiple responsibilities.
Following the best practice sometime isn't simple, and that is why every day we are working with classes with more than 500 lines of code, with many properties and methods that are very long. This happens because during the development of any software, our classes grow, and when we add new responsibilities to the class, they seem, at first, to be too minimal to delegate to a new class, and later they seem too large to move. We must not fall into this trap. When class responsibilities are too much and the class does actions that should be done by two classes, we have to take a deep breath, and create a new class. With an automatic test, we can ensure the maintenance of the initial value.
When we have methods and/or properties with similar prefixes and/or suffixes, we can extract them in a new class. When we have groups of properties that we always change together when we should change only one, because there are big dependencies, we can extract them in a new class. When it seems that some properties and methods are concerned with the same features that do not always represent the class, we can extract them in a new class.
Follow this mechanism to extract a new class:
We want to extract a new class for the following Book
class, decoupling author features to book features:
class Book
{
...
private $author_firstname;
private $author_lastname;
...
public function getAuthor()
{
return $this->author_firstname . ' ' . $this->author_lastname;
}
public function setAuthorFirstname($firstname)
{
$this->author_firstname = $firstname;
}
public function getAuthorFirstname()
{
return $this->author_firstname;
}
public function setAuthorLastname($lastname)
{
$this->author_lastname = $lastname;
}
public function getAuthorLastname()
{
return $this->author_lastname;
}
...
}
Following the mechanism, first of all we write the unit test for the Book
class, because we don't want to change and lose the original class behavior.
class BookTest extends PHPUnit_Framework_TestCase
{
...
public function testAuthorInfo()
{
$book = new Book();
$book->setAuthorFirstname('Francesco'),
$book->setAuthorLastname('Trucchia'),
$this->assertEquals('Francesco Trucchia', $book->getAuthor());
$this->assertEquals('Francesco', $book->getAuthorFirstname());
$this->assertEquals('Trucchia', $book->getAuthorLastname());
}
...
}
We run the BookTest test and it works fine.
PHPUnit 3.4.1 by Sebastian Bergmann.
.
Time: 0 seconds
OK (1 test, 3 assertions)
As stated before, we want to extract the author properties and methods from the Book
class. To do it we create a new test for our new Author
class. Doing a test first, we can better design the Author interfaces.
class AuthorTest extends PHPUnit_Framework_TestCase
{
...
public function testInfo()
{
$author = new Author();
$author->setAuthorFirstname('Francesco'),
$author->setAuthorLastname('Trucchia'),
$this->assertEquals('Francesco', $author->getAuthorFirstname());
$this->assertEquals('Trucchia', $author->getAuthorLastname());
}
...
}
We run the AuthorTest test and it fails because the Author
class does not exist. So we create the new class Author
.
class Author
{
}
Now for $author_firstname and $author_lastname properties of the Book
class we use the “move field” method to extract them in the Author
class. So we copy the properties in the new class and use the encapsulate field practice to create the accessory methods.
class Author
{
var $author_firstname;
var $author_lastname;
public function setAuthorFirstname($firstname)
{
$this->author_firstname = $firstname;
}
public function getAuthorFirstname()
{
return $this->author_firstname;
}
public function setAuthorLastname($lastname)
{
$this->author_lastname = $lastname;
}
public function getAuthorLastname()
{
return $this->author_lastname;
}
}
We run AuthorTest and it works fine.
PHPUnit 3.4.1 by Sebastian Bergmann.
.
Time: 0 seconds
OK (1 test, 2 assertions)
We remove properties moved from the old class, introduce a new property, $author, on the Book
class that stores an instance of the new class Author
, and, at the end, we change all code where the Book
methods use properties just removed with accessory methods of the Author
class.
class Book
{
...
private $author;
...
public function __construct()
{
$this->author = new Author();
}
public function getAuthor()
{
return $this->author->getAuthorFirstname() . ' ' . $this->author->getAuthorLastname();
}
public function setAuthorFirstname($firstname)
{
$this->author->setAuthorFirstname($firstname);
}
public function getAuthorFirstname()
{
return $this->author->getAuthorFirstname();
}
public function setAuthorLastname($lastname)
{
$this->author->setAuthorLastname($lastname);
}
public function getAuthorLastname()
{
return $this->author->getAuthorLastname();
}
...
}
We run BookTest and it works fine.
If we want to give direct access to the Author
class through the Book
class we can change the getAuthor()
method of the Book
class to return the Author object.
class Book
{
...
public function getAuthor()
{
return $this->author;
}
...
}
We run BookTest and it fails, because the interface is not more consistent with the previous behavior. Now getAuthor()
returns an object instead of a string.
PHPUnit 3.4.1 by Sebastian Bergmann.
F
Time: 0 seconds
There was 1 failure:
1) BookTest::testAuthorInfo
Failed asserting that
Author Object
(
[author_firstname] => Francesco
[author_lastname] => Trucchia
)
matches expected <string:Francesco Trucchia>.
/Users/cphp/Dropbox/Progetti/Libri/Apress/ProPHPRefactoring/chapters/code/test/BookTest.php:15
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
In PHP5 there is a magic method, __toString()
, which allows a class to decide how it will react when it is converted to a string (http://www.php.net/manual/en/language.oop5.magic.php#language.oop5.magic.tostring
). We can move the original getAuthor()
method to this magic method in the Author
class.
class Author
{
...
public function __toString()
{
return $this->getAuthorFirstname() . ' ' . $this->getAuthorLastname();
}
...
}
Now we have to change our test, forcing the typecasting to string when we call the getAuthor()
method of the Book
class.
class BookTest extends PHPUnit_Framework_TestCase
{
...
public function testAuthorInfo()
{
...
$this->assertEquals('Francesco Trucchia', (string)$book->getAuthor());
...
}
...
}
We run BookTest and it works fine.
PHPUnit 3.4.1 by Sebastian Bergmann.
.
Time: 0 seconds
OK (1 test, 3 assertions)
Later we can also redirect clients' calls using accessory Book
methods to access Author
properties directly to the new Author
accessory methods, removing the Author
accessory method from the Book
class. In this way we greatly simplify Book
class interfaces.
Problem: “A class isn't doing very much.”
Solution: “Move all its features into another class and delete it.”
The Inline
class is the opposite refactoring of the Extract
class. Sometimes Extract
class refactoring delegates responsibility to a class that remains too small for exists. Having a lot of code to maintain is a bad smell, so if we have some classes that don't do enough to be a class, we need to remove with this refactoring.
For example, if a class is used only by another class and this class has only a property and some accessory methods, maybe this class could be removed and made inline to the absorbing class that uses it.
We start from the Book
and Author
classes.
class Book
{
...
protected $author;
...
public function __construct()
{
$this->author = new Author();
}
public function getAuthor()
{
return $this->author;
}
...
}
class Author
{
protected $fullname;
public function __toString()
{
return $this->fullname;
}
public function setFullname($fullname)
{
$this->fullname = $fullname;
}
public function getFullname()
{
return $this->fullname;
}
}
The Author
class is too small and has too few properties, so we can remove it and move all properties and methods to the Book
class, which is the only class that uses it.
First we write a functional test for the Book
class. We improve the Book tests also, testing the public methods we need to declare in the Book
class to reference to the author.
class BookTest extends PHPUnit_Framework_TestCase
{
public function testAuthor()
{
$book = new Book();
$book->setAuthorFullname('Francesco Trucchia'),
$this->assertEquals('Francesco Trucchia', $book->getAuthorFullname());
$this->assertEquals('Francesco Trucchia', (string)$book->getAuthor());
}
}
While we are writing tests, we decide to rename accessory public methods of Author properties because the original names are not meaningful. Now we declare these methods in the Book
class.
class Book
{
...
public function getAuthorFullname()
{
return $this->author->getFullname();
}
public function setAuthorFullname($fullname)
{
$this->author->setFullname($fullname);
}
...
}
We run our tests, and if everything works fine we can change all clients' calls to author objects from the Book
class with the new public methods.
PHPUnit 3.4.1 by Sebastian Bergmann.
.
Time: 0 seconds
OK (1 test, 2 assertions)
Ok, so we can change clients' calls, as in the following example:
// We change this call
$book = new Book();
$book->getAuthor()->setFullname('Francesco Trucchia'),
echo $book->getAuthor()->getFullname();
// to this call
$book = new Book();
$book->setAuthorFullname('Francesco Trucchia'),
echo $book->getAuthorFullname();
At the end, with move field and move method refactoring, we can move all properties and methods from the Author
class to the Book
class, and then we can remove the Author
class safely. Do not forget to remove the associated unit tests also.
Our finished Book
class will be
class Book
{
protected $author_fullname;
public function getAuthor()
{
return $this->author_fullname;
}
public function getAuthorFullname()
{
return $this->author_fullname;
}
public function setAuthorFullname($fullname)
{
$this->author_fullname = $fullname;
}
}
Problem: “A client is calling a delegate class of an object.”
Solution: “Create methods on the server to hide the delegate.”
Inline class refactoring is used to implement encapsulation between objects. Encapsulation is “the process of compartmentalizing the elements of an abstraction that constitute its structure and behavior; encapsulation serves to separate the contractual interface of an abstraction and its implementation.” [http://en.wikipedia.org/wiki/Encapsulation_(computer_scienc
]
Encapsulation, in practice, means that objects need to know as little as possible of the other parts of the system. In this way, when the behavior of parts of the system changes, there are few items to change.
The first example of encapsulation is about classes properties. Everyone knows that the classes properties should be hidden, especially in PHP, where all properties are public by default. This is because the less that is known outside the class about its properties, the easier it will be to change it.
With more experience, we realize that this same principle can be applied to parts of more complex systems.
If a client object directly calls a method of a property of a server object, and if that method or the property changes, the client call will have to change also. If I have a lot of these calls from different objects in different parts of the system, we can easily understand that maintenance will be very difficult. To overcome this problem, many times we prefer to encapsulate the delegate class in the class server, so the client object directly calls a method of server class and not that of his property. With encapsulation, if the property or its interfaces change, it will be enough to change only the class server.
I have two classes, Project
and User
. User
can be assigned to a project, and a Project
has a manager.
class Project
{
protected $manager;
public function getManager()
{
return $this->manager;
}
public function setManager(User $manager)
{
$this->manager = $manager;
}
}
class User
{
protected $project;
public function setProject(Project $project)
{
$this->project = $project;
}
public function getProject(Project $project)
{
return $this->project;
}
}
If we want to know who is the project manager of a user, we need to use the following code:
$romei = new User();
$trucchia = new User();
$project = new Project();
$project->setManager($romei);
$trucchia->setProject($project);
$trucchia->getProject()->getManager();
We want to hide the access to the project object from the User
class, and we want to directly know who is the project manager of a user.
First of all we write a unit test for the User
class, and we add tests for all public methods of the Project
class.
class UserTest extends PHPUnit_Framework_TestCase
{
public function testProjectManager()
{
$trucchia = new User();
$romei = new User();
$project = new Project();
$project->setManager($romei);
$trucchia->setProject($project);
$this->assertEquals($romei, $trucchia->getProject()->getManager());
$this->assertEquals($romei, $trucchia->getManager());
$trucchia->setManager($trucchia);
$this->assertEquals($trucchia, $trucchia->getManager());
}
}
Now we declare delegate methods in the server class User
for each method into the delegate class Project
.
class User
{
protected $project;
public function setProject(Project $project)
{
$this->project = $project;
}
public function getProject()
{
return $this->project;
}
public function getManager()
{
return $this->project->getManager();
}
public function setManager(User $manager)
{
$this->project->setManager($manager);
}
}
We run tests and everything works fine.
We change all clients calls to Project objects through User objects, and then we remove the accessory methods getProject() to the Project
class, so clients can no longer call the methods of the Project
class through the User
class. We need to also fix our unit test removing the Project direct call.
This is our User
class after we removed the getProject()
method. The User
class must no longer display the interface to access the project property to its clients, in keeping with our refactoring goal.
class User
{
protected $project;
public function setProject(Project $project)
{
$this->project = $project;
}
public function getManager()
{
return $this->project->getManager();
}
public function setManager(User $manager)
{
$this->project->setManager($manager);
}
}
With its relative tests:
class UserTest extends PHPUnit_Framework_TestCase
{
public function testProjectManager()
{
$trucchia = new User();
$romei = new User();
$project = new Project();
$project->setManager($romei);
$trucchia->setProject($project);
$this->assertEquals($romei, $trucchia->getManager());
$trucchia->setManager($trucchia);
$this->assertEquals($trucchia, $trucchia->getManager());
}
}
Problem: “A class is doing too much simple delegation.”
Solution: “Get the client to call the delegate directly.”
In the previous section we saw how, through encapsulation, it is possible to hide direct access to attribute methods of a server class. The encapsulation is an object-oriented programming property that is very useful when we are sure that we want to hide our object properties interface, but in others it may be very restrictive and can complicate our code uselessly. For example, if the methods of the class, whose attribute is instance in the server class, change often, we will often change the code in server class methods, or we'll frequently have to add or remove methods to access the attribute properties.
When this job becomes too arduous, we can directly access attribute methods through the server class, which in this case would be the man in the middle. For this reason this refactoring is named “remove middle man,” and it is the opposite of “hide delegate” refactoring.
We need to add a new method in the server class through which we can directly access to attribute. In this way, clients can call attribute methods directly without using the delegate methods of the server class. Once you change all the calls you can remove delegate methods in the server class.
Starting from the example in the previous section, regarding “hide delegate” refactoring, we can go backwards to figure out how to remove object encapsulation.
We have User
and Project
classes:
Class User
{
protected $project;
public function setProject(Project $project)
{
$this->project = $project;
}
public function getManager()
{
return $this->project->getManager();
}
public function setManager(User $manager)
{
$this->project->setManager($manager);
}
}
Class Project
{
protected $manager;
public function getManager()
{
return $this->manager;
}
public function setManager(User $manager)
{
$this->manager = $manager;
}
}
The User
class forbids us to go directly to the Project
class, encapsulating its methods within their own. Instead the design of my code is changing, and now I need to have direct access to all public methods of the Project
class through the User
class.
In practice, now, we can access Project
class methods only through the User
class and, for example, if we want to know who the project manager is of a project to which we are assigned, we can call
echo $user->getManager();
Instead we would like to have direct access to Project
class methods, as follows:
echo $user->getProject()->getManager();
Following the refactoring mechanism, we first need to write a new test for the User
class to test the new method that we will create to directly access the Project
class properties.
class UserTest extends PHPUnit_Framework_TestCase
{
public function testProjectManager()
{
$trucchia = new User();
$romei = new User();
$trucchia->setProject(new Project());
$trucchia->getProject()->setManager($romei);
$this->assertEquals($romei, $trucchia->getProject()->getManager());
}
}
In this test, we add a test for the getProject()
method that does not exist yet in the User
class, and we will need it to directly access to $project properties and all its methods.
Now we can add the new method:
Class User
{
protected $project;
public function setProject(Project $project)
{
$this->project = $project;
}
public function getProject()
{
return $this->project;
}
public function getManager()
{
return $this->project->getManager();
}
public function setManager(User $manager)
{
$this->project->setManager($manager);
}
}
Run the test that was previously prepared and make sure everything is correct. Now we can change all clients calls to delegate methods in the User
class. For example, we can finally change the call
echo $user->getManager();
to
echo $user->getProject()->getManager();
Once we change all the calls, we run all test suites to be sure we haven't broken anything. If this happens, we correct our test until all is correct.
Finally, we can remove all delegate methods on the User
class, because now no one will use them.
Class User
{
protected $project;
public function setProject(Project $project)
{
$this->project = $project;
}
public function getProject()
{
return $this->project;
}
}
We run the test suite one last time and adjust the code if some tests are red.
Problem: “A server class you are using needs an additional method, but you can't modify the class.”
Solution: “Create a method in the client class with an instance of the server class as its first argument.”
While we are using a class included in the PHP core or from third-party libraries, such as Zend Framework, Symfony, or another, we realize that we need a functionality that the class does not implement. Damn! Why did the author not think about this essential feature? Finding complete classes is very difficult because the domains in which we use the same class can be really different, and it is almost unthinkable that a single class is perfectly adaptable to all.
At this point we need to introduce some business logic related to the server class in our class. If it happens one time, have patience, but if we begin to need the same functionality in other places, we have to wrap the functionality in a method, introducing a foreign method. This refactoring is a workaround for all those times that we cannot change the classes we are using, because obviously the best thing would be to add the method in an outer class. Being an outside virtual method, we must remember that it absolutely mustn't have visibility of the properties of our class; all parameters must be passed in the call and the first parameter must be an instance of the server class.
Method visibility should be private, because it doesn't represent the class where it is implementing. Therefore the method is not testable.
We have some code that needs to check the deadlines in a billing period. To control these deadlines we need to know what is the next day of a certain date. Starting from the Bill
class,
Class Bill
{
protected $previous_end;
protected $new_start;
public function setPreviousEnd($date)
{
$this->previous_end = new DateTime($date);
$this->new_start = clone $this->previous_end;
$this->new_start->modify('+1 day'),
}
public function getNewStart($format = 'm-d-Y')
{
return $this->new_start->format($format);
}
public function getPreviousEnd($format = 'm-d-Y')
{
return $this->previous_end->format($format);
}
}
We want to introduce a foreign method, which should be implemented in the DateTime
class, called nextDay()
, which must calculate the next day.
First of all, we write the test in the Bill
class:
class BillTest extends PHPUnit_Framework_TestCase
{
public function testNewStart()
{
$bill = new Bill();
$bill->setPreviousEnd('tomorrow'),
$this->assertEquals(date('m-d-Y', strtotime('+2 days')), $bill->getNewStart());
$this->assertEquals(date('m-d-Y', strtotime('tomorrow')), $bill->getPreviousEnd());
}
}
We include all the code inside the nextDay()
method, passing the instance of DateTime
as the first parameter and returning the new date incremented by one day.
Class Bill
{
protected $previous_end;
protected $new_start;
public function setPreviousEnd($date)
{
$this->previous_end = new DateTime($date);
$this->new_start = self::nextDay($this->previous_end);
}
public function getNewStart($format = 'm-d-Y')
{
return $this->new_start->format($format);
}
public function getPreviousEnd($format = 'm-d-Y')
{
return $this->previous_end->format($format);
}
/**
* Foreign method: should stay in DateTime class
*/
private static function nextDay(DateTime $date)
{
$next_day = clone $date;
return $next_day->modify('+1 day'),
}
}
We run all tests and all must run, because we simply added an internal interface.
In this chapter we learned how to change the responsibilities of our classes if they do not fit our software. We learned how to move the methods and properties of classes in other classes if they do not represent them. We learned how to hide or reveal access to delegated classes. We also learned how to extract new classes from a class that is too empowered and delete unnecessary classes. In the next chapter, we'll discuss how to better organize data.
18.117.75.236