C H A P T E R  8

image

Changing Class Responsibilities

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.

Move Method

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.”

Motivation

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.

Mechanics

  • Write a unit test for our class. This test will ensure that we don't lose value already acquired with refactoring.
  • Identify the method to move. Usually the best candidates are those methods that deal with activities that should be delegated to another class using, for example, many properties of this class and not of the class where they are defined.
  • Observe the attributes used inside the method. If it uses only some attributes of the class, we have to move them also. If attributes are also used by other methods, we must understand whether to move these methods. If these attributes strongly characterize the class where they are defined, we cannot move them. We have to pass them as parameters to the method we are moving.
  • Check that the method doesn't extend the methods of a super class or it will be extended by some subclass. Where it does so, we must check whether we can move the method.
  • Write a unit test for the destination class.
  • Add a test that designs and tests the behavior of the new moving method.
  • Define the method in the target class. Often we can also change the method name by giving it a name that better represents what it does.
  • Copy the method code from the class source to the destination class. Adjust the code so that everything is working fine in this new position. If the method uses attributes of the class source, we must understand whether these attributes can be moved in class destination or passed as parameters. If the method includes some exceptions, we have to decide whether to move these exceptions too or keep them in the source class. As for parameters, we can understand how to move based on domain exceptions.
  • Run a unit test for the destination class and fix the tests until all are fixed.
  • Change the source method and let it be a delegated method.
  • Run a unit test for the source class. Fix all tests until they are fixed. Probably, if we use a mock class for the destination class, we have to adjust the mock class code also, so that tests could run.
  • Decide whether to remove the source completely or keep it as a delegated method. If the method has public visibility and is called in many parts of our software, it is easier to keep it. If we have good code coverage with our tests, we can be bold and delete it. Tests will show where we have to replace the call.

Example

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.

Move Property (or Field)

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.”

Motivation

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.

Mechanics

  • Write unit tests for the source and target classes where we want to move property.
  • Define a new property in the target class with the same name of the source class property we want to move.
  • Add the accessory methods to the new property in the target class.
  • Run tests for the target class and fix it until all errors are fixed.
  • Understand how to reference to the new object instead of the property. If there is a method or a property that already does it, we can use it. If we don't have this possibility, we can add a new property to store the object.
  • Remove the property, just moved, from the source class.
  • Change the property call in the source class with the right accessory methods of the target class.
  • Run a test for the source class and fix it until all errors are fixed.

Example

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->image
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->image
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->image
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.

Extract Class

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.”

Motivation

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.

Mechanics

Follow this mechanism to extract a new class:

  • Create a new unit test for the source class.
  • Decide which properties and/or methods to extract.
  • Create a new test for the new class and for each new method you are moving. If class or method names aren't meaningful, give them better names.
  • Create the new class.
  • In the old class constructor, store an instance of the new class in an existent property or in a new one.
  • Use the “move field” method to move each property in the new class.
  • Run new and old class tests. Fix it until all errors are fixed.
  • Use the “move method” to move each method in the new class.
  • Run new and old class tests. Fix it until all errors are fixed.
  • Decide how to expose the new class through the old class. Decide whether to modify the new class through the old class, or only to access it without modifying it.

Example

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.

Inline Class

Problem: “A class isn't doing very much.”

Solution: “Move all its features into another class and delete it.”

Motivation

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.

Mechanics

  • Find the absorbing class to which to move all properties and methods of the class to remove.
  • Write unit tests for the absorbing class. It needs to declare all public methods from the source class to the absorbing class, so add tests for these new methods.
  • Declare a public method into the absorbing class referenced to the source class.
  • Run tests. Fix them if they are broken.
  • Change all clients' calls to the source class with calls to the absorbing class's new methods.
  • Run all suite tests. Fix them until all errors are fixed.
  • Use move field and move method refactoring to move all properties and methods from the source class to the absorbing class.
  • Run tests. Fix them until all errors are fixed.

Example

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;
  }
}

Hide Delegate

Problem: “A client is calling a delegate class of an object.”

Solution: “Create methods on the server to hide the delegate.”

Motivation

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.

Mechanics

  • Detect the delegate class to hide.
  • Write a unit test for the server class and add a test for each method you need to declare to hide delegate methods.
  • Declare methods to hide delegate methods from the client.
  • Run tests and fix until all errors are fixed.
  • Change all calls from clients to delegate methods class.
  • Run all suite tests.
  • Remove accessory methods to the delegate class from the server class.

Example

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

Remove the Middle Man

Problem: “A class is doing too much simple delegation.”

Solution: “Get the client to call the delegate directly.”

Motivation

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.

Mechanism

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.

  • Write a test for the method that will access the delegate class.
  • Add the new method in the server class.
  • Run the test and fix it until all errors are fixed.
  • Change all clients calls to go directly to the attribute through the new method.
  • Remove all delegate methods by the server class.
  • Correct server class tests, removing those tests relating to the methods just removed.
  • Run all test suites and fix the code until all errors are fixed.

Example

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 Userclass.

In practice, now, we can access Project class methods only through the Userclass 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.

Introduce Foreign Method

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.”

Motivation

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.

Mechanism

  • Write a test for the client class.
  • Run tests and all must run.
  • Create the foreign method in the client class.
  • The first parameter of the method must be an instance of the server.
  • Comment method as “Foreign method: should be in server class.”
  • Run tests and all must run.

Example

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.

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.75.236