C H A P T E R  12

image

Simplifying Generalization Relationships

With the generalization methods we want to introduce order and clarity in class hierarchies. There are methods that deal with moving class features in hierarchy to the subclass or to the super class (pull up/down attributes, pull up/down method), there is a method that takes care of standardizing the constructor and extracts it on the super class (pull up constructor body), and another very interesting method that helps us to implement the template method pattern of “Gang of Four”[GOF], which is useful when we have methods that perform the same steps but the steps are different.

We will see how to extract subclasses or super classes, useful when the single class tends to encapsulate different behaviors in different instances, or to help us understand how to collapse a hierarchy when useless.

Using inheritance in object-oriented programming is a good habit, but sometimes it can create confusion—for example, when some super class methods or parameters do not represent their subclasses, and it would be better to implement a delegation,. In this chapter we will also see how we can replace inheritance with delegation or replace the delegation with inheritance.

The collection of refactoring techniques that we'll present in this chapter was created by Martin Fowler [FOW01]. For each method, we'll see the motivation and situation for using it, the mechanism that will explain how we can apply the method to our existing code, and some examples of how each method works in a real-world case.

Pull Up Field

Problem: “Two subclasses have the same field.”

Solution: “Move the field to the super class.”

Motivation

When we find two classes that extend the same super class having the same attributes, we can move these duplicated attributes in the super class.

It may happen that the same attributes have different names. In this case, check that the attributes are equal, and that they are used in the same way. If we are sure, we can rename the attribute in one of two classes, move it to the super class and remove them from their subclasses.

Mechanism

  • Write unit tests to confirm that the behavior doesn't change after refactoring.
  • Find similar attributes.
  • Check that they are used in the same way.
  • If attributes do not have the same name, rename one of the two with the same name of the other.
  • Run tests and make sure everything still works properly.
  • Create a new attribute in the super class with protected visibility so that the subclasses can access it.
  • Delete the attributes in the subclasses.
  • Run tests and make sure everything still works properly.

Example

After some refactoring steps, we have two classes—User and Admin—that extend the BaseUser class. These two classes were initially independent, and therefore both have the same attribute $username.

class User extends BaseUser
{
  public $username;

  public function getUsernameWithLabel()
  {
    return 'User: '.$this->username;
  }
}

class Admin extends BaseUser
{
  public $username;

  public function getUsernameWithLabel()
  {
    return 'Admin: '.$this->username;
  }
}

Before moving the common attribute in the super class, we write a unit test that preserves the proper class behavior.

class UserTest extends PHPUnit_Framework_TestCase
{
  public function testGetUsername()
  {
    $admin = new Admin();
    $admin->username = 'cphp';
    $this->assertEquals('Admin: cphp', $admin->getUsernameWithLabel());

    $user = new User();
    $user->username = 'cphp';
    $this->assertEquals('User: cphp', $user->getUsernameWithLabel());
  }
}

Run the test and verify that everything works.

PHPUnit 3.4.1 by Sebastian Bergmann.

.

Time: 1 second

OK (1 test, 2 assertions)

At this point, following the mechanism, we identify similar class attributes. In our case we have the $username attribute that is duplicated in both classes. After checking that it's used in the same way both in the User class and in the Admin class, we can create a new attribute with the same name in a super class and remove it from the subclasses.

abstract class BaseUser
{
  public $username;
}

class User extends BaseUser
{
  public function getUsernameWithLabel()
  {
    return 'User: '.$this->username;
  }
}

class Admin extends BaseUser
{
  public function getUsernameWithLabel()
  {
    return 'Admin: '.$this->username;
  }
}

Run the unit test again and verify that the behavior of the classes has not changed.

Pull Up Method

Problem: “You have methods with identical results on subclasses.”

Solution: “Move them to the super class.”

Motivation

As we have said several times, duplicate code is one of the most dangerous bad smells, because where there is duplicate code, bugs can proliferate. Every time we duplicate even a single line of code, we should ask what would happen if we apply a change in one of the two lines, forgetting the other.

Through the copy and paste action often we can find the exact same methods duplicated in subclasses that extend the same super class. In this case we can move the duplicated method in the super class and delete it from all subclasses.

In other cases we have methods with the same body but with different interfaces. In these cases we must standardize the interface, move the method in the super class, and remove the original methods from the subclasses.

Another case is when we have a method of a subclass, which overrides a super class method, and the body remains unchanged, producing the same behavior.

If we move the method body into the super class, but we have other methods and attributes that aren't in the super class, we can implement some strategies If we're using attributes present only in a subclass, we can move these attributes to a super class. If we're using methods present only in a subclass we can move these methods to a super class if all subclasses use it, or create an abstract method on a super class.

If we have similar methods that are not exactly equal, we can apply the “Form Template” technique, which we'll see after in this chapter.

Mechanism

  • Identify methods that appear to be duplicated.
  • Check that the behavior of the methods is the same. If methods do the same thing but in different ways, you can align the two bodies through the technique of “Replacement Algorithm.”
  • If the methods have different interfaces, align them with a more significant interface.
  • Create a new method in the super class with the same interface, copy in the new method body one of two origin methods, fix it where needed, and run tests. If the method uses some methods present only in the subclass, declare an abstract method in the super class. If the method uses an attribute of the subclass, move the attribute in the super class, or encapsulate it and declare getter and setter methods in the abstract super class.
  • Delete the method in one of the subclasses, and run the tests.
  • For each subclass remove the method and run tests until the method remains only in the super class.

Example

Consider an abstract super class BaseUser with two subclasses, Admin and User.

abstract class BaseUser
{
  protected $credentials;

  public function hasCredential($credential)
  {
    return in_array($credential, $this->credentials);
  }
}
class Admin extends BaseUser
{
  public function initCredentials($credentials)
  {
    $this->credentials = array_merge($credentials, $this->getBaseCredentials());
  }

  public function getBaseCredentials()
  {
    return array('admin', 'base'),
  }
}

class User extends BaseUser
{
  public function initCredentials($credentials)
  {
    $this->credentials = array_merge($credentials, $this->getBaseCredentials());
  }

  public function getBaseCredentials()
  {
    return array('user', 'base'),
  }
}

The initCredential() method is the same in both classes, but the getBaseCredential() method used inside the initCredential() method is different. First of all, we write a unit test to prevent class behavior.

class UserTest extends PHPUnit_Framework_TestCase
{
  public function testGetCredential()
  {
    $admin = new Admin();
    $admin->initCredentials(array('editor'));
    $this->assertTrue($admin->hasCredential('base'));
    $this->assertTrue($admin->hasCredential('admin'));
    $this->assertTrue($admin->hasCredential('editor'));
    $this->assertTrue(!$admin->hasCredential('user'));

    $user = new User();
    $user->initCredentials(array('editor'));
    $this->assertTrue($user->hasCredential('base'));
    $this->assertTrue($user->hasCredential('user'));
    $this->assertTrue($user->hasCredential('editor'));
    $this->assertTrue(!$user->hasCredential('admin'));
  }
}

Run the test and verify that everything works. Now, before moving the method, we must define the getBaseCredentials() method as abstract in the super class.

abstract class BaseUser
{
  ...
  abstract protected function getBaseCredentials();
  ...
}

Then we can move the initCredential() method in the super class and remove it from each subclass. Every time we remove a method we run a unit test.

abstract class BaseUser
{
  protected $credentials;

  abstract protected function getBaseCredentials();

  public function hasCredential($credential)
  {
    return in_array($credential, $this->credentials);
  }

  public function initCredentials($credentials)
  {
    $this->credentials = array_merge($credentials, $this->getBaseCredentials());
  }
}

class Admin extends BaseUser
{
  protected function getBaseCredentials()
  {
    return array('admin', 'base'),
  }
}

class User extends BaseUser
{
  protected function getBaseCredentials()
  {
    return array('user', 'base'),
  }
}

Pull Up Constructor Body

Problem: “You have constructors on subclasses with mostly identical bodies.”

Solution: “Create a super class constructor; call this from the subclass methods.”

Motivation

The constructor of a class is usually the delegate method to initialize the values of the object instantiated. When we find subclasses that implement the same constructor, we can move it into the super class. In PHP, when we create an instance of a subclass, if the constructor is not implemented, it uses the constructor of the parent class.

If the constructors of the subclasses share only a part of their body, we can extract only this part and put it into a super class constructor, calling the parent constructor inside the subclass constructor. In PHP it is possible to override the parent constructor, changing its interface.

Mechanism

  • Write a unit test of the class to confirm that the behavior doesn't change after refactoring.
  • Identify the body or the partial body of constructors to extract.
  • Define the constructor in the super class.

Now, you can have two cases. In the first, you have to extract all of the constructor's body:

  • Extract the body of the subclass constructor into the constructor of the super class.
  • Remove the constructors from the subclasses.
  • Run a unit test.

In the second case, we have to extract only a part of the subclass's constructor body:

  • Move the body part of the constructor in the super class.
  • Replace the body part with a call to the constructor of the super class.
  • Run a test.

Where we have other equal code to call after the execution of the constructor of the subclass, we can extract a new method and move it in the super class.

Example

We have two classes—News and Events—that extend the Article class. After some refactoring steps we have already moved fields and methods in the super class. But now we have a part of the body of the subclasses' constructor duplicate.

class Article
{
  protected $title;
  protected $author;

  public function getTitle()
  {
    return $this->title;
  }
}

class Event extends Article
{
  protected $start_date;
  protected $end_date;

  public function __construct($title, $author, $start_date, $end_date)
  {
    $this->title = $title;
    $this->author = $author;
    $this->start_date = $start_date;
    $this->end_date = $end_date;
  }

  public function getStartDate()
  {
    return $this->start_date;
  }

  public function getEndDate()
  {
    return $this->end_date;
  }
}

class News extends Article
{
  protected $date;

  public function __construct($title, $author, $date)
  {
    $this->title = $title;
    $this->author = $author;
    $this->date = $date;
  }

  public function getDate()
  {
    return $this->date;
  }
}

Before starting the refactoring, we write two unit tests, one for the Event class and one for the News class, if we have not already done so. In the unit test we check the behavior of the constructor. We first implement the Event class unit test.

class EventTest extends PHPUnit_Framework_TestCase
{
  public function testEvent()
  {
    $event = new Event('New event', 'Francesco Trucchia', strtotime('5/10/2010 8:00 am'), image
strtotime('5/11/2010 6:00 pm'));
    $this->assertTrue($event instanceof Article);
    $this->assertEquals('New event', $event->getTitle());
    $this->assertEquals(strtotime('5/10/2010 8:00 am'), $event->getStartDate());
    $this->assertEquals(strtotime('5/11/2010 6:00 pm'), $event->getEndDate());
  }
}

Then we implement a unit test for the News class.

class NewsTest extends PHPUnit_Framework_TestCase
{
  public function testNews()
  {
    $news = new News('New news', 'Francesco Trucchia', strtotime('5/10/2010 8:00 am'));
    $this->assertTrue($news instanceof Article);
    $this->assertEquals('New news', $news->getTitle());
    $this->assertEquals(strtotime('5/10/2010 8:00 am'), $news->getDate());
  }
}

Run the tests and make sure that everything works right. If the tests are running properly, we can begin the steps of refactoring. The first step is to identify the duplicated block of code in the constructor to move to the super class. Looking at the class constructors News and Events, we realize that the first two lines are the same. We implement the constructor method in the super class and extract the two lines of code.

class Article
{
  ...
  public function construct($title, $author)
  {
    $this->title = $title;
    $this->author = $author;
  }
  ...
}

Then replace the two lines just copied with the call to the constructor of the super class.

class News extends Article
{
  ...
  public function __construct($title, $author, $date)
  {
    parent::__construct($title, $author);
    $this->date = $date;
  }
  ...
}

class Event extends Article
{
  ...
  public function __construct($title, $author, $start_date, $end_date)
  {
    parent::__construct($title, $author);
    $this->start_date = $start_date;
    $this->end_date = $end_date;
  }
  ...
}

We run tests and make sure that everything runs correctly.

Push Down Method

Problem: “Behavior on a super class is relevant only for some of its subclasses.”

Solution: “Move it to those subclasses.”

Motivation

The “Push Down” method is the opposite of the “Move Up” method. We can use this refactoring technique when a certain method in the super class is meaningful only for a certain subclass. In this case you must move the method from the super class to a subclass.

Mechanism

  • Write a unit test of the class to confirm that the behavior doesn't change after refactoring.
  • Identify the method to move down.
  • Implement the method in each subclass. If the attributes to method access are private, we must change their visibility, becoming protected, or implement the accessory methods if we cannot change the visibility.
  • Remove the method from the super class.
  • Run a unit test.
  • Remove the origin method from the subclasses.
  • Remove the method from subclasses that it doesn't represent.
  • Run the unit test again.

Example

Consider an abstract super class BaseUser with two subclasses, Premium and User.

abstract class BaseUser
{
  ...
  public function getPaymentDate()
  {
    return $this->payment_date + $this->duration_time;
  }

  public function haveToPay()
  {
    return $this->getPaymentDate() < strtotime('today'),
  }
  ...
}
class Premium extends BaseUser
{
  ...
}

class User extends BaseUser
{
  ...
}

Methods getPaymentDate() and haveToPay() are used only with Premium instances and never with User. So we want to push down these two methods, from the BaseUser class to the Premium class. First, write a test to confirm that the behavior doesn't change. We also add a check that methods getPaymentDate() and haveToPay() can't be called by the User instance.

--- PremiumTest.php ---
<?php

class PremiumTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->user = new Premium();
  }

  public function testHaveToPay()
  {
    $this->user->duration_time = 60*60*24*30;
    $this->user->payment_date = strtotime('5/1/2009'),

    $this->assertEquals('31-05-2009', date('d-m-Y', $this->user->getPaymentDate()));
    $this->assertTrue($this->user->haveToPay());

    $this->user->payment_date = strtotime('+2 days'),
    $this->assertFalse($this->user->haveToPay());
  }
}

--- UserTest.php ---

class UserTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->user = new User();
  }

  public function testHaveToPay()
  {
    $this->assertTrue($this->user instanceof BaseUser);
    $this->assertFalse(method_exists($this->user, 'getPaymentDate'));
    $this->assertFalse(method_exists($this->user, 'haveToPay'));
  }
}

If we run tests, PremiumTest is ok, and UserTest fails because getPaymentDate() and haveToPay() are still methods of the User class. The next step of refactoring is to implement the getPaymentDate() and haveToPay() methods in each subclassing and remove them from the super class.

abstract class BaseUser
{
  ...
}

class Premium extends BaseUser
{
  ...
  public function getPaymentDate()
  {
    return $this->payment_date + $this->duration_time;
  }

  public function haveToPay()
  {
    return $this->getPaymentDate() < strtotime('today'),
  }
  ...
}

class User extends BaseUser
{
  ...
  public function getPaymentDate()
  {
    return $this->payment_date + $this->duration_time;
  }

  public function haveToPay()
  {
    return $this->getPaymentDate() < strtotime('today'),
  }
  ...
}

At the end, we need to remove the two methods from the User class, because they don't represent the User object.

class User extends BaseUser
{
  ...
}

We run tests and everything works fine.

PHPUnit 3.4.1 by Sebastian Bergmann.

.

Time: 0 seconds

OK (1 test, 3 assertions)

Push Down Field

Problem: “A field is used only by some subclasses.”

Solution: “Move the field to those subclasses.”

Motivation

“Push Down Field” is the opposite of “Move Up Field.” We can use this refactoring technique when a certain attribute in the super class is significant only in one of the subclasses.

Mechanism

  • Write a unit test of the class to confirm that the behavior doesn't change after refactoring.
  • Declare the attribute in all subclasses.
  • Remove the attribute in the super class.
  • Run the unit test.
  • Remove the attribute in classes where it is not necessary.
  • Run the unit test again.

Example

After some refactoring steps we have two classes—Developer and Manager—that extend the abstract class Employee.

abstract class Employee
{
  protected $project_manager;
  protected $assigned_projects;
}

class Developer extends Employee
{
  public function setProjectManager($project_manager)
  {
    $this->project_manager = $project_manager;
  }
}

class Manager extends Employee
{
  public function setAssignedProjects($assigned_projects)
  {
    $this->assigned_projects = $assigned_projects;
  }

  public function getAssignedProjects()
  {
    return $this->assigned_projects;
  }
}

The Employee class has two properties, $project_manager and $assigned_projects. These two parameters don't represent both classes and they need to be moved to subclasses. $project_manager has to be moved to the Developer class and $assigned_projects to the Manager class.

We write a test to check that the two classes don't have the wrong parameter; if needed, we have to check their behavior to confirm that it doesn't change after refactoring.

--- ManagerTest.php ---

include(dirname(__FILE__).'/Employee.php'),

class ManagerTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->manager = new Manager();
  }

  public function testProjectManagerPropertyDoesntExists()
  {
    $reflection_manager = new ReflectionObject($this->manager);
    $this->assertFalse($reflection_manager->hasProperty('project_manager'));
  }
}

--- DeveloperTest.php ---

class DeveloperTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->developer = new Developer();
  }

  public function testAssignedProjectsPropertyDoesntExists()
  {
    $reflection_developer = new ReflectionObject($this->developer);
    $this->assertFalse($reflection_developer->hasProperty('assigned_projects'));
  }
}

Tests fail because the classes still have the wrong properties. To push down fields we need first to copy all the properties we want to move from the super class to the subclasses, and remove them from the super class.

abstract class Employee { }

class Developer extends Employee
{
  protected $project_manager;
  protected $assigned_projects;
  public function setProjectManager($project_manager)
  {
    $this->project_manager = $project_manager;
  }
}

class Manager extends Employee
{
  protected $project_manager;
  protected $assigned_projects;

  public function setAssignedProjects($assigned_projects)
  {
    $this->assigned_projects = $assigned_projects;
  }

  public function getAssignedProjects()
  {
    return $this->assigned_projects;
  }
}

At the end we can remove the fields that don't represent the class where they are—for example, we have to remove $project_manager from the Manager class, and $assigned_projects from the Developer class.

class Developer extends Employee
{
  protected $project_manager;

  public function setProjectManager($project_manager)
  {
    $this->project_manager = $project_manager;
  }
}

class Manager extends Employee
{
  protected $assigned_projects;

  public function setAssignedProjects($assigned_projects)
  {
    $this->assigned_projects = $assigned_projects;
  }

  public function getAssignedProjects()
  {
    return $this->assigned_projects;
  }
}

We run tests and confirm that everything works fine.

$ phpunit DeveloperTest.php
PHPUnit 3.4.1 by Sebastian Bergmann.

.

Time: 0 seconds

OK (1 test, 1 assertion)

---

$ phpunit ManagerTest.php
PHPUnit 3.4.1 by Sebastian Bergmann.

.

Time: 0 seconds

OK (1 test, 1 assertion

Extract Subclass

Problem: “A class has features that are used only in some instances.”

Solution: “Create a subclass for that subset of features.”

Motivation

There are some classes where it's useful to extract a subclass when we realize that a set of its features is used only in certain instances of the class. We have already seen that in other refactoring techniques, it is recommended to extract classes, such as when we have a type attribute because we can remove the attribute adding subclasses or remove it through the state/strategy pattern.

When we don't have the type attribute, we can extract the class or perform this refactoring by extracting a subclass. The leading choice in both cases is to choose between inheritance and delegation. If the object typology needs to change after being instantiated, we must delegate. Otherwise we can use the inheritance.

Mechanism

  • Write a unit test to confirm that the behavior doesn't change after refactoring.
  • Define a new subclass of the initial class.
  • Find all instances of the class source in your code and replace it with instances of the subclass where necessary. If you find that the constructor of the subclass may be different from that of the super class, we can implement it in the subclass with the new interface and then call the parent constructor into the child subclass constructor. If we see that the super class is not more instantiated, but only its subclass are instantiated, let's make it abstract.
  • Run the test and verify that it is still correct.
  • Move the methods required by the super class to the subclass.
  • Run the test again and verify that it is still correct.
  • Move the class attributes from the super class to the subclass.
  • Find each attribute of the super class that expresses the same information now expressed by heredity, and remove it, encapsulating the attributes and replacing the getter body with the constants class.
  • Run tests and verify that it is still correct.

Example

We have the Subscription class, which is used both for annual subscriptions and monthly subscriptions. Through the $is_yearly attribute we can understand whether the subscription is yearly or monthly. The method getPrice() implements different behaviors based on this attribute.

class Subscription
{
  private $price;
  private $is_yearly;
  private $discount;

  public function __construct($price, $is_yearly = false, $discount = 0)
  {
    $this->price = $price;
    $this->is_yearly = $is_yearly;
    $this->discount = $discount;
  }

  public function getPrice()
  {
    if ($this->is_yearly)
    {
      $price = $this->price * 12;
      return $price - ($price / 100 * $this->discount);
    }

    return $this->price;
  }

  public function getDiscount()
  {
    return $this->discount;
  }

  public function isYearly()
  {
    return $this->is_yearly;
  }
}

We want to extract a YearSubscription subclass from the Subscription class, in order to isolate different behaviors in different classes. Let's start writing a unit test for the Subscription class.

class SubscriptionTest extends PHPUnit_Framework_TestCase
{
  public function testSubscription()
  {
    $subscription = new Subscription(100);
    $this->assertEquals(100, $subscription->getPrice());
    $this->assertEquals(false, $subscription->isYearly());
  }

  public function testYearlysubscription()
  {
    $yearly_subscription = new Subscription(100, true, 20);
    $this->assertEquals((100*12)-((100*12)/100*20), $yearly_subscription->getPrice());
    $this->assertEquals(true, $yearly_subscription->isYearly());
  }
}

We use a unit test to move through the refactoring steps. First we want the annual subscription to be an instance of a new class called YearSubscription, so we modify the testYearlysubscription() method of the unit test, changing the class to instance.

class SubscriptionTest extends PHPUnit_Framework_TestCase
{
  ...
  public function testYearlysubscription()
  {
    $yearly_subscription = new YearSubscription(100, true, 20);
    $this->assertEquals((100*12)-((100*12)/100*20), $yearly_subscription->getPrice());
    $this->assertEquals(true, $yearly_subscription->isYearly());
  }
  ...
}

We implement the class YearSubscription that extends the class Subscription.

class YearSubscription extends Subscription { }

Now we can change the class interface of Subscription, because the $discount attribute belongs only to the YearSubscription class. To do that, we override the constructor in the YearSubscription class.

class YearSubscription extends Subscription
{
  public function __construct($price, $is_yearly = false, $discount = 0)
  {
    parent::__construct($price, $is_yearly, $discount);
  }
}

Then we modify the interface of the Subscription class and move the assignment of the $discount attribute in the constructor of the subclass. We also change the visibility of this attribute from private to protected in the super class, so we can access it in the YearSubscription subclass.

class Subscription
{
  private $price;
  private $is_yearly;
  protected $discount;

  public function __construct($price, $is_yearly = false)
  {
    $this->price = $price;
    $this->is_yearly = $is_yearly;
  }
  ...
}

In the constructor body of the YearSubscription class, we must change the call to the parent constructor, just modified, and set the $discount attribute directly.

class YearSubscription extends Subscription
{
  public function __construct($price, $discount = 0)
  {
    parent::__construct($price, true);
    $this->discount = $discount;
  }
}

Now we can move the methods that are only of the subclass. The method getDiscount() of the Subscription class is used only with the class YearSubscription, so push it down.

class YearSubscription extends Subscription
{
  public function __construct($price, $discount = 0)
  {
    parent::__construct($price, true);
    $this->discount = $discount;
  }

  public function getDiscount()
  {
    return $this->discount;
  }
}

We also push down the attribute $discount of the super class that is used only with the subclass.

class YearSubscription extends Subscription
{
  protected $discount;

  public function __construct($price, $discount = 0)
  {
    parent::__construct($price, true);
    $this->discount = $discount;
  }
  public function getDiscount()
  {
    return $this->discount;
  }
}

We also remove the $is_yearly attribute that represents the same information given by inheritance. To do this we must change the method isYearly() in the super class to return a constant value. The same thing should be done also in the subclass.

class Subscription
{
  ...
  public function isYearly()
  {
    return false;
  }
}

class YearSubscription
{
  ...
  public function isYearly()
  {
    return true;
  }
}

To remove the $is_yearly attribute, we must first remove the conditional logic of the method getPrice() with the polymorphism. In practice, we modify the method in the super class and then we do an override in the subclass. We must also change the visibility of the attribute price, otherwise the subclass cannot access it.

class Subscription
{
  protected $price;
  ...
  public function getPrice()
  {
    return $this->price;
  }
}

class YearSubscription
{
  ...
  public function getPrice()
  {
    $price = $this->price * 12;
    return $price - ($price / 100 * $this->discount);
  }
}

Now we can remove all references to the $is_yearly attribute in the Subscription class.

class Subscription
{
  protected $price;

  public function __construct($price)
  {
    $this->price = $price;
  }

  public function getPrice()
  {
    return $this->price;
  }

  public function isYearly()
  {
    return false;
  }
}

Remember to run the tests at every step of refactoring to verify that the correct behavior is maintained.

Extract Super Class

Problem: “You have two classes with similar features.”

Solution: “Create a super class and move the common features to the super class.”

Motivation

When we have two classes doing the same things in the same way, or similar things in different ways, we can extract a super class and standardize some of their behavior to avoid duplicated code.

Once we have extracted the super class, we can use all the refactoring techniques seen in this chapter to remove duplicated code. We can push attributes and methods up or down, we can rename the methods that do the same thing but are named differently and move them to the super class, we can pull up the constructor, and so on.

We can also use inheritance to remove duplication. When we cannot use inheritance, we can use delegation. It isn't always easy to know at first what is better to use. Don't worry—we use a KISS approach (http://en.wikipedia.org/wiki/K.I.S.S.), and if we are wrong, we can always refactor, replacing inheritance with delegation and vice versa.

Mechanism

  • Identify classes that do the same thing or something similar.
  • Write a unit test for each class to confirm that the behavior doesn't change after refactoring.
  • Extract a super class and make sure that the origin classes extend it.
  • Move attributes and common methods one at a time. It is best to start from the attributes, changing the visibility where needed. Then continue with the methods. Where methods have different interfaces, rename them and then move them. Where only part of the method is the same, extract the method first, and then move it to the super class. Where the bodies of the methods are different but do the same thing, try replacing the algorithm, and then move the method in the super class.
  • Run the unit test to confirm every change.
  • Check for instances of classes. If no client needs to instantiate the super class, make it abstract.

Example

We have two classes—Order and Estimate —that have the same behavior.

class Order
{
  private $id;
  private $number;
  private $details = array();

  public function setId($id)
  {
    $this->id = $id;
  }

  public function setNumber($number)
  {
    $this->number = $number;
  }

  public function addDetail($detail)
  {
    $this->details[] = $detail;
  }

  public function getTotal()
  {
    $total = 0;
    foreach($this->details as $detail)
    {
      $total += $detail->getTotal();
    }
    return $total;
  }
}

class Estimate
{
  private $id;
  private $number;
  private $details = array();
  public function setId($id)
  {
   $this->id = $id;
  }

  public function setNumber($number)
  {
   $this->number = $number;
  }

  public function addDetail($detail)
  {
   $this->details[] = $detail;
  }

  public function getTotal()
  {
    $total = 0;
    foreach($this->details as $detail)
    {
      $total += $detail->getTotal();
    }
    return $total;
  }
}

We want to extract the super class Document in order to remove the duplication of code, because both classes have the same attributes and the same methods. First of all, we write a unit test for each class.

class DocumentIntegrationTest extends PHPUnit_Framework_TestCase
{
  private function detail($price, $amount, $description)
  {
    $detail = new Detail();
    $detail->setPrice($price);
    $detail->setAmount($amount);
    $detail->setDescription($description);
    return $detail;
  }

  public function testOrder()
  {
    $order = new Order();
    $order->setId(10);
    $order->setNumber(100);
    $order->addDetail($this->detail(100, 10, 'Detail 1'));

    $this->assertEquals(1000, $order->getTotal());
  }

  public function testEtimate()
  {
    $estimate = new Estimate();
    $estimate->setId(10);
    $estimate->setNumber(100);
    $estimate->addDetail($this->detail(100, 10, 'Detail 1'));

    $this->assertEquals(1000, $estimate->getTotal());
  }
}
?>
</pre>

Create a super class Document and make sure that the classes Order and Estimate extend it. Add a test that checks the inheritance to the unit test.

class Document {}

class Order extends Document { ... }

class Estimate extends Document { ... }

class DocumentIntegrationTest extends PHPUnit_Framework_TestCase
{
  public function testOrder()
  {
    ...
    $this->assertTrue($order instanceof Document);
    $this->assertEquals(1000, $order->getTotal());
  }

  public function testEtimate()
  {
    ...
    $this->assertTrue($estimate instanceof Document);
    $this->assertEquals(1000, $estimate->getTotal());
  }
}

We run the unit test and verify it is correct. If everything is ok, we can begin to pull up the attributes of the duplicated classes in the super class Document, changing their visibility where needed. In our case we pull up the $id, $number, and $details attributes, changing their visibility from private to protected. Once moved in the super class, we can remove them from the subclasses.

class Document
{
  protected $id;
  protected $number;
  protected $details = array();
}

Then pull up the methods from the subclasses to the super class, one at a time, and run the unit tests at each time. After moving the method, remove it from the other subclasses.

class Document
{
  protected $id;
  protected $number;
  protected $details = array();
  public function setId($id)
  {
   $this->id = $id;
  }

  public function setNumber($number)
  {
   $this->number = $number;
  }

  public function addDetail($detail)
  {
   $this->details[] = $detail;
  }

  public function getTotal()
  {
    $total = 0;
    foreach($this->details as $detail)
    {
      $total += $detail->getTotal();
    }
    return $total;
  }
}

class Estimate extends Document { }

class Order extends Document { }

Collapse Hierarchy

Problem: “A super class and subclass are not very different.”

Solution: “Merge them together.”

Motivation

When we realize that a single class hierarchy doesn't give value to any of the classes in the hierarchy, we can do a merge of the hierarchy and create a single class. If we are often moving methods or attributes up and down the hierarchy, it probably means that we can't give the right meaning to any of the classes in the hierarchy. So we must merge them.

Mechanism

  • Identify the hierarchy that you want to merge and decide whether to keep the super class or subclass.
  • Write a unit test for the class we want to keep to confirm that the behavior doesn't change after refactoring.
  • Move the class methods and attributes up or down as needed.
  • Run the unit test and check that everything is ok.
  • Adjust calls to the class that you want to remove, including calls to its methods or attributes.
  • Run the unit test again and check that everything is ok.
  • Remove the class.
  • Run the unit test for the last time.

Example

We have a Salesman class that extends an abstract class, Employee. The class Salesman is the only one that extends the Employee class, so we want to remove the Employee class, pushing down all methods and properties merging the hierarchy.

abstract class Employee
{
  protected $firstname;
  protected $lastname;
}

class Salesman extends Employee
{
  public function setFirstname($firstname)
  {
    $this->firstname = $firstname;
  }

  public function setLastname($lastname)
  {
    $this->lastname = $lastname;
  }

  public function getFirstname()
  {
    return $this->firstname;
  }

  public function getLastname()
  {
    return $this->lastname;
  }
}

We write a test for the Salesman class, to confirm its behavior doesn't change after merging.

class CollapseHierarchyTest extends PHPUnit_Framework_TestCase
{
  public function testCollapse()
  {
    $salesman = new Salesman();
    $salesman->setFirstname('Francesco'),
    $salesman->setLastname('Trucchia'),
    $this->assertEquals('Francesco', $salesman->getFirstname());
    $this->assertEquals('Trucchia', $salesman->getLastname());
  }
}

The Employee class has only two properties, and we move them to Salesman with the “Push Down Field” technique.

abstract class Employee
{

}

class Salesman extends Employee
{
  protected $firstname;
  protected $lastname;

  public function setFirstname($firstname)
  {
    $this->firstname = $firstname;
  }

  public function setLastname($lastname)
  {
    $this->lastname = $lastname;
  }

  public function getFirstname()
  {
    return $this->firstname;
  }

  public function getLastname()
  {
    return $this->lastname;
  }
}

Then, we remove the hierarchy from Salesman and run the unit test.

class Salesman
{
  protected $firstname;
  protected $lastname;

  public function setFirstname($firstname)
  {
    $this->firstname = $firstname;
  }

  public function setLastname($lastname)
  {
    $this->lastname = $lastname;
  }
  public function getFirstname()
  {
    return $this->firstname;
  }

  public function getLastname()
  {
    return $this->lastname;
  }
}

If the test is ok, we remove the Employee class also and run all unit tests again.

Form Template Method

Problem: “You have two methods in subclasses that perform similar steps in the same order, yet the steps are different.”

Solution: “Get the steps into methods with the same signature, so that the original methods become the same. Then you can pull them up."

Motivation

There are cases in which removing code duplication between the methods of our hierarchies may seem impossible, since these methods differ very little on features, but enough to not be able to pull methods and/or move them to the super class.

One of these cases is when we have two methods that perform operations in the same order, but the operations are different, sometimes so quite. The Gang of Four, to solve this problem, introduced the “Template Pattern”[GOF]. In essence, the pattern is to implement a method in the super class that calls each step in sequence through methods that may be on the super class or subclass. At this point, the methods of the steps, through polymorphism, can be quite different. The important thing is that they have the same interface.

Mechanism

  • Write a unit test for the class to confirm that the behavior doesn't change after refactoring.
  • Split the method in steps, so that steps of the method are identical or completely different.
  • Move the identical methods in the super class.
  • Rename the different methods if needed so that the name is the same.
  • Run the unit test every time you rename a method.
  • Move one of the origin methods in the super class and define step methods as abstract in the super class.
  • Run the unit test.
  • Remove duplicate methods by subclasses.
  • Run the unit test for the last time.

Example

The Article class provides two methods for printing the article in HTML or TEXTILE format (http://en.wikipedia.org/wiki/Textile_%28markup_language%29).

class Article
{
  ...
  public function texttileView()
  {
    $output = 'h1. '.$this->getTitle().PHP_EOL.PHP_EOL;
    $output .= $this->getIntro().PHP_EOL.PHP_EOL;
    $output .= $this->getBody().PHP_EOL.PHP_EOL;
    $output .= '_Written by '.$this->getAuthor().' on '.date('m/d/Y', $this->getDate()).'_';
    return $output;
  }

  public function htmlView()
  {
    $output = '<h2>'.$this->getTitle().'</h2>'.PHP_EOL;
    $output .= '<p>'.$this->getIntro().'</p>'.PHP_EOL;
    $output .= '<p>'.$this->getBody().'</p>'.PHP_EOL;
    $output .= '<em>Written by '.$this->getAuthor().' on '.date('m/d/Y', image
$this->getDate()).'</em>';
    return $output;
  }
}

These two methods are very similar because they implement the same steps, but each step is very different from each other. Therefore we want to remove the duplication of some code introducing the “Template Pattern” [GOF]. Before starting our refactoring, we extract a new class from the article class, which will be delegated only to print the template.

Extract the ArticleView class from the Article class that implements only the textileView() and htmlView() methods.

class ArticleView
{
  public function textileView($article)
  {
    $output = 'h1. '.$article->getTitle().PHP_EOL.PHP_EOL;
    $output .= $article->getIntro().PHP_EOL.PHP_EOL;
    $output .= $article->getBody().PHP_EOL.PHP_EOL;
    $output .= '_Written by '.$article->getAuthor().' on '.date('m/d/Y', $article->getDate()).'_';
    return $output;
  }

  public function htmlView($article)
  {
    $output = '<h2>'.$article->getTitle().'</h2>'.PHP_EOL;
    $output .= '<p>'.$article->getIntro().'</p>'.PHP_EOL;
    $output .= '<p>'.$article->getBody().'</p>'.PHP_EOL;
    $output .= '<em>Written by '.$article->getAuthor().' on '.date('m/d/Y', image
$article->getDate()).'</em>';
    return $output;
  }
}
class Article
{
  ...
  public function textileView()
  {
    $view = new ArticleView();
    return $view->textileView($this);
  }

  public function htmlView()
  {
    $view = new ArticleView();
    return $view->htmlView($this);
  }
  ...
}

From the ArticleView class, we can extract two subclasses, ArticleTextile and ArticleHtml. One implements the method textileView(), and the other implements the method htmlView(). As the method name would be redundant with the class name, rename both methods in view(). Once the class is extracted, we also fix the calls to the class on clients.

class ArticleView
{

}

class ArticleTextile extends ArticleView
{
  public function view($article)
  {
    $output = 'h1. '.$article->getTitle().PHP_EOL.PHP_EOL;
    $output .= $article->getIntro().PHP_EOL.PHP_EOL;
    $output .= $article->getBody().PHP_EOL.PHP_EOL;
    $output .= '_Written by '.$article->getAuthor().' on '.date('m/d/Y', $article->getDate()).'_';
    return $output;
  }
}

class ArticleHtml extends ArticleView
{
  public function view($article)
  {
    $output = '<h2>'.$article->getTitle().'</h2>'.PHP_EOL;
    $output .= '<p>'.$article->getIntro().'</p>'.PHP_EOL;
    $output .= '<p>'.$article->getBody().'</p>'.PHP_EOL;
    $output .= '<em>Written by '.$article->getAuthor().' on '.date('m/d/Y', image
$article->getDate()).'</em>';
    return $output;
  }
}

class Article
{
  ...
  public function textileView()
  {
    $view = new ArticleTextile();
    return $view->view($this);
  }

  public function htmlView()
  {
    $view = new ArticleHtml();
    return $view->view($this);
  }
  ...
}
?>
</pre>

We run the tests and make sure that everything is correct. At this point we can start implementing the “Form Template” technique for the view() method of the ArticleTextile and ArticleHtml classes. Split the method into steps so that all the steps are separated. We start from the class ArticleTextile.

class ArticleTextile extends ArticleView
{
  protected function title($article)
  {
    return 'h1. '.$article->getTitle().PHP_EOL.PHP_EOL;
  }

  protected function intro($article)
  {
    return $article->getIntro().PHP_EOL.PHP_EOL;
  }

  protected function body($article)
  {
    return $article->getBody().PHP_EOL.PHP_EOL;
  }

  protected function footer($article)
  {
    return '_Written by '.$article->getAuthor().' on '.date('m/d/Y', $article->getDate()).'_';
  }

  public function view($article)
  {
    return $this->title($article)
          .$this->intro($article)
          .$this->body($article)
          .$this->footer($article);
  }
}

We do the same thing for the ArticleHtml class.

class ArticleHtml extends ArticleView
{
  protected function title($article)
  {
    return '<h2>'.$article->getTitle().'</h2>'.PHP_EOL;
  }

  protected function intro($article)
  {
    return '<p>'.$article->getIntro().'</p>'.PHP_EOL;
  }

  protected function body($article)
  {
    return '<p>'.$article->getBody().'</p>'.PHP_EOL;
  }

  protected function footer($article)
  {
    return '<em>Written by '.$article->getAuthor().' on '.date('m/d/Y', image
$article->getDate()).'</em>';
  }

  public function view($article)
  {
    return $this->title($article)
          .$this->intro($article)
          .$this->body($article)
          .$this->footer($article);
  }
}

Finally we can move the view() method in the super class ArticleView, and remove it from the subclasses.

class ArticleView
{
  public function view($article)
  {
    return $this->title($article)
          .$this->intro($article)
          .$this->body($article)
          .$this->footer($article);
  }
}

class ArticleTextile extends ArticleView
{
  protected function title($article)
  {
    return 'h1. '.$article->getTitle().PHP_EOL.PHP_EOL;
  }
  protected function intro($article)
  {
    return $article->getIntro().PHP_EOL.PHP_EOL;
  }

  protected function body($article)
  {
    return $article->getBody().PHP_EOL.PHP_EOL;
  }

  protected function footer($article)
  {
    return '_Written by '.$article->getAuthor().' on '.date('m/d/Y', $article->getDate()).'_';
  }
}

class ArticleHtml extends ArticleView
{
  protected function title($article)
  {
    return '<h2>'.$article->getTitle().'</h2>'.PHP_EOL;
  }

  protected function intro($article)
  {
    return '<p>'.$article->getIntro().'</p>'.PHP_EOL;
  }

  protected function body($article)
  {
    return '<p>'.$article->getBody().'</p>'.PHP_EOL;
  }

  protected function footer($article)
  {
    return '<em>Written by '.$article->getAuthor().' on '.date('m/d/Y', image
$article->getDate()).'</em>';
  }
}

We run the unit test for the last time and make sure that everything works fine.

Replace Inheritance with Delegation

Problem: “A subclass uses only part of a super class's interface or does not want to inherit data.”

Solution: “Create a field for the super class, adjust methods to delegate to the super class, and remove the subclass.”

Motivation

Inheritance is a very important feature of object-oriented programming. We must use it in the right way, otherwise it could create confusion. When we have classes that use few methods of the super class, classes that are not represented in the data and methods of their super class, or represented only partially, we must ask whether the choice of inheritance is the right choice.

Sometimes, it can be right anyway, but others may be better off using delegation rather than inheritance. Through delegation we explain in a better way what we need of the delegated class, how it represents our class, and what we can ignore. There is a payable fee, which is rewriting many methods of the delegated class, but, on the other hand, the advantage is that it increases the code's clarity.

Mechanism

  • Write a unit test for the subclass.
  • Add a new attribute on the subclass referred to the super class. Assign an instance of object itself to this attribute.
  • Change each subclass method that uses super class fields, with the delegated fields.
  • Remove the hierarchy from the subclass and assign to the super class attribute an instance of a new super class object.
  • Add a delegating method for each super class method used by the subclass.
  • Run the unit test.

Example

The class Car extends the class Engine, but it uses only one attribute of this class, the attribute $CV. So the Engine class is only partially described from the Car class. In fact, the engine should be an attribute of the Car class and not its super class, since there is a more general set of the car. We can confirm that in this case it would be better to use delegation rather than inheritance.

class Engine
{
  protected $fuel_type;
  protected $kW;
  protected $CV;

  public function getFuel()
  {
    return $this->fuel;
  }

  public function getKW()
  {
    return $this->kW;
  }

  public function getCV()
  {
    return $this->CV;
  }

  public function setFuel($fuel)
  {
    $this->fuel = $fuel;
  }

  public function setKW($kw)
  {
    $this->kW = $kw;
  }

  public function setCV($cv)
  {
    $this->CV = $cv;
  }
}

class Car extends Engine
{
  protected $brand;
  protected $model;

  public function __toString()
  {
    return $this->brand.' '.$this->model.' ('.$this->getCV().'CV)';
  }

  public function setModel($model)
  {
    $this->model = $model;
  }

  public function getModel()
  {
    return $this->model;
  }

  public function setBrand($brand)
  {
    $this->brand = $brand;
  }

  public function getBrand()
  {
    return $this->brand;
  }

}

Following the mechanism, we must first write a unit test for the Car subclass.

class CarTest extends PHPUnit_Framework_TestCase
{
  public function testEngine()
  {
    $car = new Car();
    $car->setBrand('Audi'),
    $car->setModel('A3'),
    $car->setCV('250'),

    $this->assertEquals('Audi', $car->getBrand());
    $this->assertEquals('A3', $car->getModel());
    $this->assertEquals('250', $car->getCV());

    $this->assertEquals('Audi A3 (250CV)', (string)$car);
  }
}

We run the test and verify that it doesn't fail. To replace inheritance, we must, first of all, create an attribute $engine in the Car class that is an instance of itself.

class Car extends Engine
{
  ...
  protected $engine;

  public function __construct()
  {
    $this->engine = $this;
  }
  ...
}

Replace, in the methods of the Car class, the calls to methods or attributes of the super class with the delegated attribute. In our case, the only method that makes use of attributes of the super class is the __toString() method.

class Car extends Engine
{
  ...
  public function __toString()
  {
    return $this->brand.' '.$this->model.' ('.$this->engine->getCV().'CV)';
  }
  ...
}

Remove inheritance from the Car class and assign to the $engine attribute a new instance of object engine.

class Car
{
  ...
  protected $engine;

  public function __construct()
  {
    $this->engine = new Engine();
  }
  ...
}

Finally, add the proxy method setCV() in the Car class to set the attribute $CV of the Engine class.

class Car
{
  ...
  public function setCV($cv)
  {
    $this->engine->setCV($cv);
  }
  ...
}

Run the unit test and verify that everything is correct.

Replace Delegation with Inheritance

Problem: “You're using delegation and are often writing many simple delegations for the entire interface.”

Solution: “Make the delegating class a subclass of the delegate.”

Motivation

This technique is the opposite of the technique “Replace Inheritance with Delegation.” When a class uses all the methods of a delegate class, and when we add a new method in the delegate class, we must always add it in the delegating class. We can “Replace Delegation with Inheritance.”

If the delegating class doesn't use all the methods of the delegate class, we cannot use this refactoring technique, because with inheritance all methods of the super class become interfaces for subclasses. If we cannot replace the delegation, we can use other methods. For example, we can directly make access clients to the delegated class, so we don't have to rewrite all delegate methods, or we can extract a super class from the delegate class to isolate the common interface and then inherit from it.

If the delegate class is shared with other objects and may change after you have instantiated it, we cannot replace it with inheritance, because then we couldn't share it.

Mechanism

  • Make the delegating class a subclass of the delegate class.
  • Set the delegate attribute as an instance of the object itself.
  • Remove the delegate methods.
  • Run the unit test.
  • Replace all other delegations with attributes or methods of the object itself.
  • Remove the delegate attribute.

Example

We have an Employee class, without attributes, which delegates all its behaviors to the Person class.

class Person
{
  protected $name;

  public function setName($name)
  {
    $this->name = $name;
  }

  public function getName()
  {
    return $this->name;
  }

  public function getLastName()
  {
    list(,$lastname) = explode(' ', $this->getName());
    return $lastname;
  }
}

class Employee
{
  protected $person;

  public function __construct()
  {
    $this->person = new Person();
  }

  public function __toString()
  {
    return 'Emp: '.$this->person->getLastName();
  }

  public function getName()
  {
    return $this->person->getName();
  }

  public function setName($name)
  {
    $this->person->setName($name);
  }
}

Write a unit test to confirm that the behaviors remain unchanged at the end of refactoring.

class EmployeeTest extends PHPUnit_Framework_TestCase
{
  public function testEngine()
  {
    $employee = new Employee();
    $employee->setName('Francesco Trucchia'),

    $this->assertEquals('Emp: Trucchia', (string)$employee);
    $this->assertEquals('Trucchia', $employee->getLastName());
    $this->assertEquals('Francesco Trucchia', $employee->getName());
  }
}

Define inheritance from the Person class in the Employee class.

class Employee extends Person {...}

We run the tests and make sure everything is still correct. Afterwards, we assign an instance of the object itself to the delegate attribute and remove accessory methods.

class Employee extends Person
{
  protected $person;

  public function __construct()
  {
    $this->person = $this;
  }

  public function __toString()
  {
    return 'Emp: '.$this->person->getLastName();
  }
}

At this point, we can directly call the methods of the Person class and remove the delegate attribute.

class Employee extends Person
{
  public function __toString()
  {
    return 'Emp: '.$this->getLastName();
  }
}

Run the tests and verify that the behaviors don't change.

Summary

In this chapter we learned how to simplify and modify our class hierarchies. We learned how to move methods and properties up and down in hierarchical classes, how to collapse unnecessary hierarchies, and how to extract super classes or subclasses in the presence of over-responsible classes. Learning how to manage generalization facilitates a very simple and easily maintainable code.

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

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