C H A P T E R  9

image

Dealing with Data Rationalization

Encapsulation means the hiding of data and behavior from a client. It is a key object-oriented concept and, in some ways, the key to object-oriented programming. Since our goal is to make each part as independent as possible from anything external, classes and methods should receive as much information as is needed to satisfy the task they are meant for.

Anyone interested in good object-oriented development cares about encapsulation and is likely concerned with hiding or exposing the best data structure in their object, depending on the needed architecture. In this chapter we'll learn how to unlock or prevent access to data with proper interfaces, how to couple the data with the behavior processing them and how to manage type-oriented code in the proper way, leading to a better organized structure.

Self-Encapsulate Field

Problem: “Accessing a field directly can lead to bad maintainability and errors.”

Solution: “Create getter and setter methods.”

Motivation

The most important scenario that will make you need this refactoring is when a super class directly accessing an attribute is extended by a subclass needing to override the access to that attribute with added logic. With a self-encapsulated attribute you are free to manage logic all along the hierarchy.

Mechanics

  1. Create getting and setting methods for the attribute.
  2. Find and replace all references to the attribute with a getter or setter method call.
  3. Make the attribute private.
  4. Run tests.

Example

This is a very easy refactoring. Let's see the class and the unit test:

class Sale
{
  public $product_price = 10;
  public $amount;

  public function getPrice()
  {
    return $this->amount * $this->product_price;
  }
}
class SaleTest extends PHPUnit_Framework_TestCase
{
  public function testGetPrice()
  {
    $sale = new Sale();
    $sale->amount = 10;
    $this->assertEquals(100, $sale->getPrice());
  }
}

Now we apply the refactoring steps and obtain the refactored class, still passing tests.

class Sale
{
  protected $product_price = 10;
  public $amount;

  public function getProductPrice()
  {
    return $this->product_price;
  }

  public function getPrice()
  {
    return $this->amount * $this->getProductPrice();
  }
}

The benefit becomes clearer if we have to extend the Sale class in DiscountedSale.

class DiscountedSale extends Sale
{
  public function getProductPrice()
  {
    return $this->product_price * 0.9;
  }
}

This passes the following test:

public function testExtendedGetPrice()
{
  $sale = new DiscountedSale();
  $sale->amount = 10;
  $this->assertEquals(90, $sale->getPrice());
}

Replace Data Value with Object

Problem: “Some data needs additional behavior.”

Solution: “Turn the data into an object.”

Motivation

You start developing a class trying to keep things simple, and you just represent data with simple data types. As the software grows, though, some of the simple data attributes you wrote need to get more complex. A simple username is just a string at the very beginning, but as you add firstname, lastname, address, e-mail, etc. this data set gets a sense of its own and likely starts showing the need to acquire independence and get some encapsulated behavior. Turning this data into an object solves the problem.

Mechanics

  • Create the class to hold the value, featuring a getter and a constructor that accept the attribute as an argument.
  • If you want, you can add a type hint in the signature of all methods accepting the attribute as a parameter.
  • Make the getter in the source class call the getter in the new class.
  • If the source class's constructor uses the attribute, make it use the new class constructor.
  • Change the setter method in the source class to create a new instance of the new class.
  • Run tests.

Example

We have the Order class with its private attribute $customer.

class Order
{
  private $customer;

  public function __construct($customer)
  {
    $this->setCustomer($customer);
  }

  public function setCustomer($customer)
  {
    $this->customer = $customer;
  }

  public function getCustomer()
  {
    return $this->customer;
  }
}

We can write a very simple test to check this class's behavior.

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function testGetCustomer()
  {
    $order = new Order('Edwin Moses'),
    $this->assertEquals('Edwin Moses', $order->getCustomer());
  }
}

After verifying that the test is OK, start refactoring the class. First, we create the Customer class:

class Customer
{
  private $name;

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

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

Thereafter we change Order's setter to create an instance of Customer and the getter to reference Customer's name getter:

class Order
{
  private $customer;

  public function __construct($customer)
  {
    $this->setCustomer($customer);
  }
  public function getCustomer()
  {
    return $this->customer->getName();
  }

  public function setCustomer($customer)
  {
    $this->customer = new Customer($customer);
  }
}

As you may have noticed already we created a Customer object for each Order, and we didn't reference a unique Customer object shared among many Order objects. This is because we are treating the customer as a value object. If you want to give the customer her own identity, the next refactoring technique “Change Value to Reference” addresses this need.

Change Value to Reference

Problem: “You have many equal instances of a class that you want to replace with a single object.”

Solution: “Turn the object attribute into a reference object.”

Motivation

Objects can be often grouped in two well-defined categories: reference objects and value objects.

Reference objects are those standing for a given object in the real world. They are defined by identity, and that identity is checked when testing for those objects to be equal. In domain-driven design [Evans] they are called entities. User accounts in a system or a purchase order are typical examples.

Value objects have no identity. The intention of a value object is to represent something by its attributes only. Two value objects are identical if they have identical attributes. However, not having any value other than by virtue of their attributes, they can be freely copied around. Dates, money, and RGB colors are usually represented as value objects.

The decision to change a value to a reference comes after having a small initial value grow in complexity as long as development goes on. At some point you want to add some mutable data to that value and have the same changes shared across objects using that value: you then change value to reference.

Mechanics

  • Use “Replace Constructor with Factory Method.”
  • Run tests.
  • Decide what object is responsible for providing access to the objects.
  • Determine whether the objects are referenced with a Proxy/Flyweight pattern [GOF] or created when needed. If you rely on preloaded objects, make sure they get loaded before they are needed.
  • Change the factory method to return the reference object.
  • Run tests.

Example

We'll be using “Replace Data Value with Object” as a starting point for this example. We have a Customer class:

class Customer
{
  private $name;

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

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

This is used to create customer instances as value objects in an Order class:

class Order
{
  private $customer;

  public function __construct($customer)
  {
    $this->setCustomer($customer);
  }

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

  public function setCustomer($customer)
  {
    $this->customer = new Customer($customer);
  }
}

We follow with a test checking its correctness:

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function testGetCustomer()
  {
    $order = new Order('Edwin Moses'),
    $this->assertEquals('Edwin Moses', $order->getCustomer());
  }
}

We can now use “Replace Constructor with Factory Method.”

class Customer
[...]
public static function getInstance($name)
{
  return new Customer($name);
}

private function __construct($name)
{
  $this->name = $name;
}
[...]

And replace the call to the Customer constructor in the Order setter method with the factory method:

public function setCustomer($customer)
{
  $this->customer = Customer::getInstance($customer);
}

The factory method also facilitates testing the classes. In the real world, the Customer factory method would probably talk with the DB mapper or with an identity map to retrieve new or preloaded instances of the Customer class. In our case we simplify the reading using a Customer stub class to test the Order class. Our stub preloads a small set of Customer instances and the factory method will be changed to retrieve one of those preloaded instances.

class Customer
{
  private $name;

  private static $customers;

  public static function loadCustomers()
  {
    self::$customers = array(
      'Edwin Moses' => new Customer('Edwin Moses'),
      'John Foo'    => new Customer('John Foo')
    );
  }

  public static function getInstance($name)
  {
    return self::$customers[$name];
  }

  private function __construct($name)
  {
    $this->name = $name;
  }

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

Add this setup method in our unit test:

class OrderTest extends PHPUnit_Framework_TestCase
{
  [...]
  public function setUp()
{
  Customer::loadCustomers();
}
  [...]
}

Change Reference to Value

Problem: “A reference object is immutable and hard to manage.”

Solution: “Turn it into a value object.”

Motivation

The definition of a value object is in its consistent and not mutable state. This means that any time you call a method on a value object you will always get the same output in return. This property makes value objects interchangeable, thus many clients invoking a query on a reference object and getting the same result can easily be linked to many different objects representing the same value. On the other hand, if in changing a value you don't have to ensure every other object using that value must be updated, again there is no problem in using a value object.

If working with a reference becomes awkward for any reason and it can be represented by many value objects, then you can make things simpler with “Change Reference to Value.”

Mechanics

  • Check that the candidate object is (or can become) immutable. If the candidate object can mutate its state, don't perform this refactoring.
  • Remove any existing factory methods and make the constructor public.
  • Run tests.

Replace Array with Object

Problem: “You have an array containing elements meaning different things.”

Solution: “Replace the array with an object featuring an attribute for each array element.”

Motivation

Since the very beginning of computer science, arrays were meant to store collections of homogenous data. As the years passed, modern languages let us freely aggregate several types of data. PHP arrays are very flexible and provide a single construct to represent arrays, hashes, structs, and records (a few well-known examples in other languages). By the way, classes can sometimes be more expressive, and, above all, they provide the fittest ground to make behavior grow. As soon as this data is processed by very dedicated code, this is likely to be encapsulated in a class along with that data. Replacing arrays with objects, then, is the way to go.

Mechanics

  • Create a new class to move data into. Create a public field in it to host the array.
  • Make all users of the array use the new class.
  • Run tests.
  • Add meaningful setters and getters for an array element and make users of that array element call those accessors. Run tests and repeat for each array element.
  • Make array fields private.
  • Run tests.
  • For each array element create a related field in the class and change related accessors accordingly. Run tests and repeat for each array element.
  • Delete the array.

Example

We consider a simple Order class using some data about the customer:

class Order{
  private $customer;

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

  public function getShippingAddress()
  {
    return $this->customer[0].', '.$this->customer[1];
  }
}

This is tested with the following:

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function testGetCustomer()
  {
    $order = new Order(array('Gerardo Rossi', 'BeckerStrasse 12, Utopia'));
    $this->assertEquals('Gerardo Rossi, BeckerStrasse 12, Utopia', $order->getShippingAddress());
  }
}

We can then apply the refactoring steps. First we create a new class with a public array field and make the Order class use it:

class Customer
{
  public $data;
}

class Order
{
  private $customer;

  public function __construct($customer_data)
  {
    $this->customer = new Customer;
    $this->customer->data = $customer_data;
  }

  public function getShippingAddress()
  {
    return $this->customer->data[0].', '.$this->customer->data[1];
  }
}

At this point, tests are strictly green. Then we create accessors for the two fields:

class Customer
{
  private $data;

  public function getName()
  {
    return $this->data[0];
  }

  public function getAddress()
  {
    return $this->data[1];
  }
}

class Order
{
  public function __construct($customer_data)
  {
    $this->customer = new Customer;
    $this->customer->setName($customer_data[0]);
    $this->customer->setAddress($customer_data[1]);
  }
  public function getShippingAddress()
  {
    return $this->customer->getName().', '.$this->customer->getAddress();
  }
}

Now we've set the $data field private in the Customer class. Last we turn the array elements into Customer class fields and get rid of the array:

class Customer
{
  private $name, $address;

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

  public function getAddress()
  {
    return $this->address;
  }

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

  public function setAddress($address)
  {
    $this->address = $address;
  }
}

Tests continue to show that everything was changed within safe boundaries.

Change Unidirectional Association to Bidirectional

Problem: “You have two classes needing each other's features, but there is only a one-way link.”

Solution: “Add a backward link and change the setter and getter methods to update both sets.”

Motivation

You have a class attribute referring to another class. As development goes on you find that an instance of the referred class needs to get the object referring to it. The referred class features no reference to the referring class, so we have to set up a two-way reference.

Mechanics

  • Add an attribute for the back link.
  • Decide which class will control the association.
  • Create any needed helper method on the non-controlling side of the association.
  • If the existing setter is on the controlling side, modify it to update the back link.
  • If the existing setter is on the controlled side, create a controlling method on the controlling side and call it from the existing setter method.

Example

This time we have to unit test more than a class—we have to test a relationship, and it would require building correct stubs for both sides of the association. It would not be hard and, indeed, we could just arrange the right stub set in minutes, but for the sake of clarity here we will see another kind of test: integration tests. They are based on more than one real class and will be useful here to keep the example concise.

Please note how a simple dependency like the one we are about to model and test makes things lots harder to code and test, giving us strong clues about how harmful dependencies can be for our architecture.

Let's now dive into the “Change Unidirectional Association to Bidirectional” technique. Let's start from a simple version of the one Customer–many Orders relationship:

class Customer
{
  private $name;

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

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

class Order
{
  private $customer;

  public function __construct($customer)
  {
    $this->setCustomer($customer);
  }

  public function getCustomer()
  {
    return $this->customer;
  }

  public function setCustomer(Customer $customer)
  {
    $this->customer = $customer;
  }
}

This is tested by:

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->customer = new Customer('Edwin Moses'),
  }

  public function testGetCustomer()
  {
    $order = new Order($this->customer);
    $this->assertEquals($this->customer, $order->getCustomer());
  }
}

Following the refactoring steps, we add an attribute to the Customer class to hold the back link to its Order objects, and we also add a couple of helper methods here, having decided the Order class will be controlling the association:

class Customer
{
  private $name;
  private $orders = array();

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

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

  public function getOrders()
  {
    return $this->orders;
  }

  public function setOrders($orders)
  {
    $this->orders = $orders;
  }
}

Now we have to change the setter method in the Order class.

public function setCustomer(Customer $customer)
{
  if (!is_null($this->customer))
  {
    $this->customer->setOrders(array_diff($this->customer->getOrders(), array($this)));
  }
  $this->customer = $customer;
  $this->customer->setOrders(array_merge($this->customer->getOrders(), array($this)));
}

Then create a test for the Customer class:

class CustomerTest extends PHPUnit_Framework_TestCase
{
  public function testGetOrders()
  {
    $customer = new Customer('Edwin Moses'),
    $order1 = new Order($customer);
    $order2 = new Order($customer);

    $this->assertEquals(array($order1, $order2), $customer->getOrders());
  }
}

After running both tests, they should be green now.

Change Bidirectional Association to Unidirectional

Problem: “Two classes reference each other but one class no longer needs other class's features.”

Solution: “Make the two-way association one-way.”

Motivation

Bidirectional associations are sometimes needed but they always introduce complexity. This complexity comes in the form of maintaining the two-way links and avoiding errors while writing the code to manage them.

Bidirectional associations also force an interdependency between two classes, leading to a more coupled system that is harder to maintain or change without cascading and unpredictable side effects.

Thus bidirectional associations should be used only when needed. If a bidirectional association is no longer providing its benefit, make it unidirectional.

Mechanics

  • Check if the link attribute you want to remove is removal-safe. You must look at getter methods and methods using those accessors.
  • If the client needs the getter, use the “Self-Encapsulate Field” technique, and perform “Substitute Algorithm” on the getter. Also consider adding the object as an argument to all methods that use the field you are about to remove.
  • Run tests.
  • When no attribute reader is left, remove all updates to the attribute and remove the attribute. If the attribute is assigned many times, use the “Self-Encapsulate Field” technique to use a single setter, then empty its body. At this point remove the setter, all setter calls, and the attribute.
  • Run tests.

Example

The hardest step while performing this refactoring is the first step: being sure the bidirectional association is not needed anymore. If a reference to the object that the reference we are killing points to is still needed by some method, we can opt to pass that object as an argument of that method.

Let's consider an example where the Order can be discounted on a per-Customer basis.

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->customer = new Customer('Edwin Moses'),
  }

  public function testGetCustomer()
  {
    $order = new Order();
    $this->customer->addOrder($order);
    $this->assertEquals($this->customer, $order->getCustomer());
  }

This integration test is satisfied by these two classes:

class Customer
{
  private $name;
  private $orders = array();
  public $discount;

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

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

  public function addOrder(Order $order)
  {
    $order->setCustomer($this);
  }

  public function getOrders()
  {
    return $this->orders;
  }

  public function setOrders($orders)
  {
    $this->orders = $orders;
  }
}

class Order
{
  private $customer;
  private $price = 100;

  public function getCustomer()
  {
    return $this->customer;
  }

  public function setCustomer(Customer $customer)
  {
    if (!is_null($this->customer))
    {
      $this->customer->setOrders(array_diff($this->customer->getOrders(), array($this)));
    }
    $this->customer = $customer;
    $this->customer->setOrders(array_merge($this->customer->getOrders(), array($this)));
  }

  public function getDiscountedPrice()
  {
    return $this->price * $this->customer->discount;
  }
}

We want to remove the reference to Customer in the Order class. We need that reference in the getDiscountedPrice() method, so we decide to pass it as an argument:

public function getDiscountedPrice($customer)
{
  return $this->price * $customer->discount;
}

We change the test accordingly, modifying testGetDiscountedPrice():

public function testGetDiscountedPrice()
{
  $this->customer->discount = 0.7;
  $order = new Order();
  $this->customer->addOrder($order);
  $this->assertEquals(70, $order->getDiscountedPrice($this->customer));
}

Now it will pass green.

We can then remove the testGetCustomer() method from the test and remove the getter from the Order class. The tests are still green.

Now we want to remove the setter method, but it's still used in the addOrder() method in the Customer class. We perform “Substitute Algorithm” on it:

public function addOrder(Order $order)
{
  $this->orders[] = $order;
}

We are now free to remove the setCustomer() method from the Order class along with the $customer attribute, keeping tests green.

class Order
{
  private $price = 100;

  public function getDiscountedPrice($customer)
  {
    return $this->price * $customer->discount;
  }
}

Replace Magic Number with Symbolic Constant

Problem: “You have a literal meaningful number.”

Solution: “Create a meaningful constant and replace the number with it.”

Motivation

Magic numbers are those usually-not-obvious numbers with special values. The gravitational constant and pi are good examples of magic numbers in the fields of physics and mathematics.

Hardcoding magic numbers leads to less maintainable code. First, they are harder to change, and second, it's a lot harder to read the code and understand it. Declaring constants in a PHP class is easy, and they are cheap enough from a performance point of view, providing a great improvement in maintainability and readability.

Mechanics

  • Declare a constant and set it to the magic number's value.
  • Find and replace all occurrences of the magic number.
  • Run tests.

Example

This a very simple case to exemplify. Let's have a look at a class called Circle that stores its radius providing a method to compute its circumference.

class Circle
{
  public $radius;

  public function getCircumference()
  {
    return $this->radius * 2 * 3.1416;
  }
}

This is tested by:

class CircleTest extends PHPUnit_Framework_TestCase
{
  public function testGetCircumference()
  {
    $circle = new Circle();
    $circle->radius = 2;
    $this->assertEquals(12.5664, $circle->getCircumference());
  }
}

We apply the simple steps just described and we get:

class Circle
{
  const PI = 3.1416;

  public $radius;

  public function getCircumference()
  {
    return $this->radius * 2 * self::PI;
  }
}

And it still passes the test.

Encapsulate Field

Problem: “A class exposes a public attribute.”

Solution: “Make the attribute private and add accessor methods.”

Motivation

As we have already seen in “Self-Encapsulate Field,” accessing a field directly from within a class can be a choice subject to many arguments. The issue becomes even worse as we face this situation regarding some public field used by some foreign client. Encapsulation is one of the fundamentals of object-oriented programming: never make your data public. Making an attribute public means that other objects can edit the attribute with the owning object not knowing this, then making its state unknown.

This leads to distributed logic that harms the modularity of the software. Data and behavior should go together in a close relationship because it is easier to change the code in just one place rather than across the whole application.

The technique “Encapsulate Field” hides the data and adds the accessors, paving the way for one or more uses of “Move Method” to bring scattered logic into one place.

Mechanics

  • Create getter and setter methods.
  • Find all client references to the attribute. Change attribute readings with getter calls and attribute assignments with setter calls. If the attribute is an object and the client uses a modifier on it, then keep the call to the modifier.
  • Run tests after each change.
  • Declare the attribute as private.
  • Run tests.

Example

We start from where we left with the technique “Replace Magic Number with Symbolic Constant,” and we apply the steps to refactor the Circle class, beginning with adding the radius setter:

public function setRadius($radius)
{
  $this->radius = $radius;
}

fixing the test to make it green again:

public function testGetCircumference()
{
  $circle = new Circle();
  $circle->setRadius(2);
  $this->assertEquals(12.5664, $circle->getCircumference());
}

Green tests allow us to make the $radius attribute private:

class Circle
{
  const PI = 3.1416;

  private $radius;

  public function setRadius($radius)
  {
    $this->radius = $radius;
  }

  public function getCircumference()
  {
    return $this->radius * 2 * self::PI;
  }
}

Replacing Type Code with Subclasses

Problem: “You have an immutable type code affecting the class behavior.”

Solution: “Replace the type code with subclasses.”

Motivation

Quite often we have to write conditional code based on the value of a type code. Switches and if-then-else constructs are the spots to look for this kind of behavior, where we test the value of the type code to execute different code depending on that value. This conditional code needs to be refactored with the technique “Replace Conditional with Polymorphism.” For this refactoring to be performed we need the type code to be replaced by a class hierarchy that will structure the polymorphic behavior, featuring a subclass for each type code.

The simplest way to create this structure is to use the technique “Replace Type Code with Subclasses.” You take the class featuring the type code and extend it once for each type code. As long as the type code is immutable and the parent class is not already subclassed for another reason, you can apply this refactoring. If one of these conditions is not verified then you should perform “Replace Type Code with State/Strategy.”

Another reason to use “Replace Type Code with Subclasses” is the presence of type code–relevant behavior. After this refactoring you can use “Push Down Method” and “Push Down Field” to make specific data closer to specifically related behavior. This will free clients of the source class from managing variants on their side, leaving you free to add new behavior without the clients even knowing it: all you need to do is add a subclass.

Mechanics

  • Self-encapsulate the type code, replacing the constructor with a factory method if needed.
  • Create a subclass for each type code value. Override the type code accessor in the new subclass to return the relevant code. This value can even be hardcoded in the method return by now.
  • Run tests after each type code is replaced with a subclass.
  • Remove type code attributes in the parent class. Declare the type code accessor method and the whole parent class as abstract.
  • Run tests.

Example

Let's consider an Order class and its test:

class Order:

{
  const GOLD = 0;
  const SILVER = 1;
  const BRONZE = 2;
  protected $promotion;

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

class TestableOrder extends Order
{
  public function getPromotion()
  {
    return $this->promotion;
  }
}

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function testPromotionType()
  {
    $order = new TestableOrder(Order::GOLD);
    $this->assertEquals(Order::GOLD, $order->getPromotion());
    $order = new TestableOrder(Order::SILVER);
    $this->assertEquals(Order::SILVER, $order->getPromotion());
    $order = new TestableOrder(Order::BRONZE);
    $this->assertEquals(Order::BRONZE, $order->getPromotion());
  }
}

Note how we extended the tested class to facilitate its testing with a public method to probe its internal state. Since we are passing the type code into the constructor, we create a factory method after using “Self-Encapsulate Field” type code:

class Order
{
  const GOLD = 0;
  const SILVER = 1;
  const BRONZE = 2;

  protected $promotion;

  static public function create($promotion)
  {
    return new Order($promotion);
  }

  private function __construct($promotion)
  {
    $this->promotion = $promotion;
  }

  public function getPromotion()
  {
    return $this->promotion;
  }
}

We need to change tests accordingly, and we can get rid of the TestableOrder class since we introduced a type code accessor in the tested class, too:

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function testPromotionType()
  {
    $order = Order::create(Order::GOLD);
    $this->assertEquals(Order::GOLD, $order->getPromotion());
    $order = Order::create(Order::SILVER);
    $this->assertEquals(Order::SILVER, $order->getPromotion());
    $order = Order::create(Order::BRONZE);
    $this->assertEquals(Order::BRONZE, $order->getPromotion());
  }
}

We can now start creating the subclasses for the first type:

class BronzeOrder extends Order
{
  public function getPromotion()
  {
    return Order::BRONZE;
  }
}

Now we edit the factory method:

class Order
{
  [...]
  static public function create($promotion)
  {
    if ($promotion == self::BRONZE)
    {
      return new BronzeOrder($promotion);
    }
    else
    {
      return new Order($promotion);
    }
  }
  [...]
}

And tests continue to tell us that everything is OK. We can then create other subclasses related to SILVER and BRONZE promotions until we get to the end:

abstract class Order
{
  const GOLD = 0;
  const SILVER = 1;
  const BRONZE = 2;
  static public function create($promotion)
  {
    if ($promotion == self::BRONZE)
    {
      return new BronzeOrder($promotion);
    }
    elseif ($promotion == self::SILVER)
    {
      return new SilverOrder($promotion);
    }
    elseif ($promotion == self::GOLD)
    {
      return new GoldOrder($promotion);
    }
    else
    {
      throw new Exception('Invalid promotion'),
    }
  }

  private function __construct($promotion)
  {
    $this->promotion = $promotion;
  }

  abstract public function getPromotion();
}

class BronzeOrder extends Order
{
  public function getPromotion()
  {
    return Order::BRONZE;
  }
}

class SilverOrder extends Order
{
  public function getPromotion()
  {
    return Order::SILVER;
  }
}

class GoldOrder extends Order
{
  public function getPromotion()
  {
    return Order::GOLD;
  }
}

At this point you are free to move behavior up and down the hierarchy to avoid duplication or enhance specialization.

Replace Type Code with State/Strategy

Problem: “You have a type code affecting the class behavior, but you cannot use subclassing.”

Solution: “Replace the type code with a state object.”

Motivation

This is similar to the technique “Replace Type Code with Subclasses” but can be used in more general cases—if the type code changes after having created the typed class instance or if another reason prevents subclassing. State and strategy patterns [GoF] are similar and any distinction among the two is not useful here.

Mechanics

  • Self-encapsulate the type code.
  • Create a new class and name it after the purpose of the type code.
  • Add one subclass of the new class for each type code.
  • Create a data type abstract query method in the new class.
  • Override the abstract query method in each subclass to return correct type code.
  • Run tests.
  • Create a new attribute in the old class for the new state object.
  • Make the type code query method in the old class delegate to the new class query method.
  • Make the type code setting method in the old class assign an instance of the right subclass.
  • Run tests.

Example

We start from the now usual Order class to compute a discount policy depending on the Order type

class Order
{
  const GOLD = 0;
  const SILVER = 1;
  const BRONZE = 2;

  private $price = 100;
  private $promotion;

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

    switch ($this->promotion)
    {
      case self::GOLD:
        $price *= 0.7;
        break;
      case self::SILVER:
        $price *= 0.8;
        break;
      case self::BRONZE:
        $price *= 0.9;
        break;
    }

    return $price;
  }
}

We also provide tests to verify the method's correctness:

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function testPromotion()
  {
    $order = new Order(Order::GOLD);
    $this->assertEquals(70, $order->getFinalPrice());
    $order = new Order(Order::SILVER);
    $this->assertEquals(80, $order->getFinalPrice());
    $order = new Order(Order::BRONZE);
    $this->assertEquals(90, $order->getFinalPrice());
  }
}

We proceed with self-encapsulating the promotion field.

class Order
{
  [...]
  public function __construct($promotion)
  {
    $this->setPromotion($promotion);
  }

  public function getPromotion()
  {
    return $this->promotion;
  }

  public function setPromotion($promotion)
  {
    $this->promotion = $promotion;
  }
  public function getFinalPrice()
  {
    $price = $this->price;

    switch ($this->getPromotion())
    {
      [...]
    }

    return $price;
  }
}

We have now to declare the state class now. We will name it Promotion

abstract class Promotion
{
  abstract public function getCode();
}

Now we can now start creating subclasses.

class GoldPromotion extends Promotion
{
  public function getCode()
  {
    return Order::GOLD;
  }
}

class SilverPromotion extends Promotion
{
  public function getCode()
  {
    return Order::SILVER;
  }
}

class BronzePromotion extends Promotion
{
  public function getCode()
  {
    return Order::BRONZE;
  }
}

We run tests and everything is fine. Now is the moment to link the Order class to the Promotion hierarchy.

class Order
{
  [...]
  public function getPromotion()
  {
    return $this->promotion->getCode();
  }

  public function setPromotion($promotion)
  {
    switch ($promotion)
    {
      case self::GOLD:
        $this->promotion = new GoldPromotion();
        break;
      case self::SILVER:
        $this->promotion = new SilverPromotion();
        break;
      case self::BRONZE:
        $this->promotion = new BronzePromotion();
        break;
      default:
        throw new Exception('Invalid promotion code'),
    }
  }
  [...]
}

Tests are still green! Now we can polish things a bit by putting all the code using the type codes in the new class.

class Order
{
  private $price = 100;
  private $promotion;

  public function __construct($promotion)
  {
    $this->setPromotion($promotion);
  }

  public function getPromotion()
  {
    return $this->promotion->getCode();
  }

  public function setPromotion($promotion)
  {
    $this->promotion = Promotion::create($promotion);
  }

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

    switch ($this->getPromotion())
    {
      case Promotion::GOLD:
        $price *= 0.7;
        break;
      case Promotion::SILVER:
        $price *= 0.8;
        break;
      case Promotion::BRONZE:
        $price *= 0.9;
        break;
    }

    return $price;
  }
}

abstract class Promotion
{
  const GOLD = 0;
  const SILVER = 1;
  const BRONZE = 2;

  static public function create($promotion)
  {
    switch ($promotion)
    {
      case self::GOLD:
        return new GoldPromotion();
      case self::SILVER:
        return new SilverPromotion();
      case self::BRONZE:
        return new BronzePromotion();
      default:
        throw new Exception('Invalid promotion code'),
    }
  }

  abstract public function getCode();
}

class GoldPromotion extends Promotion
{
  public function getCode()
  {
    return Promotion::GOLD;
  }
}

class SilverPromotion extends Promotion
{
  public function getCode()
  {
    return Promotion::SILVER;
  }
}

class BronzePromotion extends Promotion
{
  public function getCode()
  {
    return Promotion::BRONZE;
  }
}

And we update tests to reflect this little change:

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function testPromotion()
  {
    $order = new Order(Promotion::GOLD);
    $this->assertEquals(70, $order->getFinalPrice());
    $order = new Order(Promotion::SILVER);
    $this->assertEquals(80, $order->getFinalPrice());
    $order = new Order(Promotion::BRONZE);
    $this->assertEquals(90, $order->getFinalPrice());
  }
}

Replace Subclass with Fields

Problem: “You have subclasses that vary only in methods that return constant data.”

Solution: “Change the methods to super class fields and eliminate the subclasses.”

Motivation

Constant methods are those returning a hardcoded value. Though they can be very useful in subclasses returning different values for an accessor defined in the super class, it is sometimes true that a subclass made only of constant methods is not worth its own existence. You can then remove those subclasses, move fields up to the super class, and get a less complex architecture with no subclassing.

Mechanics

  • Use the technique “Replace Constructor with Factory Method” on the subclasses.
  • Replace any reference to any subclass with a reference to the super class.
  • Declare attributes on the super class for each constant method.
  • Declare a protected super class constructor to initialize the attributes.
  • Add or modify subclass constructors to call the new super class constructor.
  • Run tests.
  • Move each constant method from each subclass to the super class to return the related attribute.
  • Run tests after each move.
  • Use the “Inline Method” technique to inline the constructor into the factory method of the super class.
  • Run tests.
  • Remove subclass.
  • Run tests.
  • Repeat constructor inlining, subclass removal, and test cycle until all subclasses are removed.

Example

We have an abstract Promotion class extended by some subclasses defining the promotion type:

abstract class Promotion
{
  abstract public function isGold();
  abstract public function isSilver();
  abstract public function getCode();
}

class GoldPromotion extends Promotion
{
  public function isGold()
  {
    return true;
  }

  public function isSilver()
  {
    return false;
  }

  public function getCode()
  {
    return 0;
  }
}

class SilverPromotion extends Promotion
{
  public function isGold()
  {
    return false;
  }

  public function isSilver()
  {
    return true;
  }

  public function getCode()
  {
    return 1;
  }
}

The following tests certify their correctness:

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function testPromotion()
  {
    $order = new GoldPromotion();
    $this->assertEquals(0, $order->getCode());
    $this->assertTrue($order->isGold());
    $this->assertFalse($order->isSilver());
    $order = new SilverPromotion();
    $this->assertEquals(1, $order->getCode());
    $this->assertFalse($order->isGold());
    $this->assertTrue($order->isSilver());
  }
}

Now we can move on, applying the first step described in the mechanics. We perform “Replace Constructor with Factory Method.”

abstract class Promotion
{
  [...]
  static public function createGoldPromotion()
  {
    return new GoldPromotion();
  }

  static public function createSilverPromotion()
  {
    return new SilverPromotion();
  }
}

Then edit tests accordingly:

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function testPromotion()
  {
    $order = Promotion::createGoldPromotion();
    $this->assertEquals(0, $order->getCode());
    $this->assertTrue($order->isGold());
    $this->assertFalse($order->isSilver());
    $order = Promotion::createSilverPromotion();
    $this->assertEquals(1, $order->getCode());
    $this->assertFalse($order->isGold());
    $this->assertTrue($order->isSilver());
  }

We can add fields to manage each different kind of promotion:

abstract class Promotion
{
  private $is_gold = false;
  private $is_silver = false;
  private $code;
  [...]
}

Now we define a protected constructor in the Promotion parent class and we use it in subclass constructors:

abstract class Promotion
{
  [...]
  protected function __construct($code)
  {
    if (0 === $code)
    {
      $this->is_gold = true;
    }
    elseif (1 === $code)
    {
      $this->is_silver = true;
    }
    else
    {
      throw new Exception('Invalid promotion code.'),
    }

    $this->code = $code;
  }
}

We also do the following:

class GoldPromotion extends Promotion
{
  public function __construct()
  {
    parent::__construct(0);
  }
  [...]
}

class SilverPromotion extends Promotion
{
  public function __construct()
  {
    parent::__construct(1);
  }
  [...]
}

At this point we can run tests to verify our code's health. Thus we can move constant methods from subclasses up to the parent class, obtaining the following:

abstract class Promotion
{
  private $is_gold = false;
  private $is_silver = false;
  private $code;

  public function isGold()
  {
    return $this->is_gold;
  }

  public function isSilver()
  {
    return $this->is_silver;
  }

  public function getCode()
  {
    return $this->code;
  }
  [...]
}

And do the following:

class GoldPromotion extends Promotion
{
  public function __construct()
  {
    parent::__construct(0);
  }
}

class SilverPromotion extends Promotion
{
  public function __construct()
  {
    parent::__construct(1);
  }
}

We must remember to run tests after moving each of the three methods to check everything is right while we refactor our code, keeping ourselves in the green zone.

We just have to perform “Inline Method” on the subclass constructors, moving the subclasses' creation directly into the parent class's factory methods.

class Promotion
{
  [...]
  static public function createGoldPromotion()
  {
    return new Promotion(0);
  }

  static public function createSilverPromotion()
  {
    return new Promotion(1);
  }
}

Now we can get rid of the subclasses.

Summary

Objects are defined by data with some behavior attached. Though the most important side of their design lies in the interface they expose, still, the data they carry plays a crucial role. Objects' inner structure can influence the interface they expose and the right choice can make a developer's life a lot easier. In this chapter we saw a few techniques to better deal with data held inside our objects. In the next chapter we'll be back focusing on our methods, discovering ways to simplify our conditional expressions.

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

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