Using a builder to construct complex classes

Tags:

Sometimes if you have a complex class, which needs to be set up in a specific way: Perhaps it takes a lot of constructor arguments, or often requires calling some setters.

Having something like that can make it more difficult to understand the code. In a case like this, you may want to consider using a builder class, which is kind of like a factory, but focused on just creating a single object. It can also be used to hide which concrete class the code is actually using by abstracting the construction.

Let’s take a look at the builder pattern in PHP. We’ll even get to use some chaining – everyone loves chaining, right?

The idea

The basic idea is that by creating a separate class for constructing the other, you can provide a more readable interface, and you can also use an abstract builder and more than one concrete builder to create different kinds of the same object without the actual usage-code having to know. Depending on the implementation, it may also be used to create multiple instances of an object with the same configuration.

This is done using methods in the builder class: By calling these methods, the builder can store the values in itself, and when done, an additional method call then returns the created object.

Implementation

Let’s create a simple message-builder – we could use this to for example build a Zend_Mail instance for sending email, or it could also be a class which sends an instant message using Jabber.

class MessageBuilder {
  private $_mail;
 
  public function __construct() {
    $this->_mail = new Email()
  }
 
  public function setReceiver($receiver) {
    $this->_mail->setReceiver($receiver);
  }
 
  public function setSender($sender) {
    $this->_mail->setSender($sender);
  }
 
  public function getMessage() {
    return $this->_mail;
  }
  /* maybe some other methods here */
}

Since this one is quite simple, it reflects the class we’re building quite closely. However, in a more complex example the benefits of this approach start to show.

Adding chaining/fluency

The MessageBuilder example could be improved to provide a more fluent and readable interface. We can specialize the builder more to include a better interface for such:

class FluentMessageBuilder {
  private $_mail;
 
  private function __construct() {
    $this->_mail = new Email()
  }
 
  public static function to($receiver) {
    $builder = new self;
    $builder->_setReceiver($receiver);
 
    return $builder;
  }
 
  private function _setReceiver($receiver) {
    $this->_mail->setReceiver($receiver);
    return $this;
  }
 
  public function from($sender) {
    $this->_mail->setSender($sender);
    return $this;
  }
 
  public function createMessage() {
    return $this->_mail;
  }
 
  /* maybe some other methods here */
}

We’ve renamed and rescoped some methods. We made the constructor private and added a static method for creating a messagebuilder, and we also made the setReceiver method private so it can’t be called outside the class, and renamed setSender to from.

Let’s compare the usage of this and the earlier class:

$builder = new MessageBuilder();
$builder->setReceiver('some@address');
$builder->setSender('other@address');
$message = $builder->getMessage();
 
//vs fluent interface:
$message = FluentMessageBuilder::to('some@address')
         ->from('other@address')
         ->createMessage();

I think the fluent version is much more readable, and again it will especially show when you have a more complex scenario.

Ensuring you always get a “complete” object

One of the benefits of using a builder is that you can usually assume that you’ll get a relatively complete object from it.

However, looking at our message example, we could still get a message without a sender or without a receiver from the builder, which may not be something to consider “complete”.

Completeness depends a lot on how you want to use the builder, but let’s assume for the sake of the example that we want to always get messages that have both sender and receiver.

This also offers a chance to look at a bit more complex version of the builder, which can be used for more complex building cases!

Inheriting builder

We’ll use a base builder-class and then we’ll define each “step” in the process as a subclass. That way we can control what the next step of the process is, and we can also ensure that the resulting object can only be built at specific steps.

//this is our base class
abstract class AbstractMessageBuilder {
  public function createMessage() {
    //This throw an exception by default so we can control when
    //we consider the object we're building complete
    throw new Exception('Incomplete');
  }
}
 
//this is the first step class
class MessageBuilder extends AbstractMessageBuilder {
  public static function to($receiver) {
    $mail = new Email();
    $mail->setReceiver($receiver);
    return new MessageBuilder_From($mail);
  }
}
 
//this is the second step
class MessageBuilder_From extends AbstractMessageBuilder {
  private $_mail;
 
  public function __construct($message) {
    $this->_mail = $message
  }
 
  public function from($sender) {
    $this->_mail->setSender($sender);
    return new MessageBuilder_Create($this->_mail);
  }
}
 
//this is the final step
class MessageBuilder_Create extends AbstractMessageBuilder {
  private $_mail;
 
  public function __construct($message) {
    $this->_mail = $message
  }
 
  //this is overridden so it won't throw an exception
  public function createMessage() {
    return $this->_mail;
  }
}

So now we have three step classes and a base. Because the base class throw an exception by default in createMessage, we can’t call it to get an incomplete message in the first step. Instead, we have to always call from, and only after that we can call createMessage.

Other than that, the functionality of this three-step builder is the same as the fluent builder’s.

In closing

By creating a builder class, you can simplify complex class creation cases, and make them more readable by providing a fluent interface.

The usefulness of builders depends on the complexity of the original procedure. There are more than one way of creating a builder, and in simple cases you should consider a factory class which is easier to implement.

Good examples of builders in popular PHP libraries are for example PHPUnit’s mocking interface and Doctrine’s DQL query building using Doctrine_Query.