/usr/portage

Antipattern: the verbose constructor 48

Constructors are often used to shortcut dependency injection and parameter passing on instantiation. This is a valid practice and often leads to shorter code. Consider the following example (a simple value object, often used to not mess around with floats and to keep currency and amount together):

class Money
{
    protected $_amount;
    protected $_currency;
    protected $_divisor;
    public function __construct(
        $amount = null, $currency = null, $divisor = null)
    {
        if ($amount !== null)
            $this->setAmount($amount);
        if ($current !== null)
            $this->setCurrency($currency);
        if ($divisor !== null)
            $this->setDivisor($divisor);
    }
    ... setter and getter ...
}

Now consider instantiating this object. Instead of creating a new instance of “Money” and calling three setter, everything can be done compactly in the constructor.

bc . $money = new Money(13200, ‘EUR’, 100);

So for the money object this works pretty well. The code is easy to read, but wait, the first argument can be grasped easily, the second too, but the third? It is not too obvious that it is a divisor is passed. An alternative would be changing the constructor to accept an array. This is a replacement for true named arguments, as e.g. Python supports. Solar uses that a lot, as well as the Zend Framework.

$money = new Money(
    array(
        'amount' => 13200,
        'currency' => 'EUR',
        'divisor' => 100
    )
);

Much better readable but does your IDE code completion works? And what happens if you pass “amoµnt”, because your fingers are as clumsy as mine? Exactly, the parameter will be silently ignored.
But look at this:

$money = new Money();
$money->setAmount(13200);
$money->setCurrency('EUR');
$money->setDivisor(100);

It is at least equally short, readable, your IDE works and if you have problems with the dimensions of your keys on your keyboard (they are too small, it has nothing to do with your fingers) you will be warned. But we could even have an even shorter example while maintaining the readability. With fluent interfaces we would get the following:

$money = new Money();
$money->setAmount(13200)->setCurrency('EUR')->setDivisor(100);

Wonderful! If you want, you can add a newline between each object operator and you would have the same amount of lines but less dense code (sad that we don’t have fluent constructors, isn’t it?). Sometimes setters are so elegant.

So until know one thing should be clear: it is not just about easily writing the code, but about the next guy understanding it too. Because you never write code for yourself. Never. But let’s investigate some real live example. I work with a framework that allows me to define really nifty business logic by just sticking together a bunch of fields and every field having a bunch of validators and filters attached.

class User extends Model
{
    protected function _define(Definition $definition)
    {
        $definition->addField(new StringField('username', true, null, true));
    }
    protected function _getStorageClass()
    {
        return 'UserStorage';
    }
}

All the time I write such a definition, I need to look into the code to check the order of the parameters. I can remember the first parameter, but the rest is too similar. To explain it: the second parameter specifies whether the field is required, the third expects a default parameter and the fourth indicates whether the value can be changed after it has been set once. I’ve talked about filters and validators, right?

class User extends Model
{
    protected function _define(Definition $definition)
    {
        $definition->addField(new StringField('username', true, null, true))
            ->addValidator(new UniqueUserValidator())
            ->addFilter(new LowercaseFilter())
            ->addValidator(new RegexValidator('/^[a-z]+$/'));
    }
}

Definition::addField() returns the passed field object to allow adding validators and filters. What works for validators and filters, should work for the rest too, shouldn’t it?

class User extends Model
{
    protected function _define(Definition $definition)
    {
        $definition->addField(new StringField('username'))
            ->setRequired(true)
            ->setReadonly(true);
    }
}

I admit, a bit more code to write, but a huge improvement in readability and therefore in maintainability. Other variants, where setter are not a good solution is to create an expressive factory. We e.g. have a Criteria object that creates and orders Criterion objects internally. Because we don’t have a fluent constructor, we have a static create-method for the Criteria object.

$criteria = Criteria::create('User')->field('id')->equal(1);

The alternative with just utilizing the constructor would be horribly to read and would have limitations regarding the parameter parsing capabilities (except if func_get_args() is used, which is totally the opposite of the paradigm of strict APIs). But back to the constructor only example:

$criteria = new Criteria('User', array('id' => 1));

And how would you express “id not equal 1” with it? So that’s where expressive factories are an alternative.

Constructors, as like any other method, should have as less parameters as possible but as much as needed. Obvious. The constructor should only allow setting vital information for the object (if the object has a name, there is a good chance, that the name is the parameter of the class’ constructor because it is considered vital). And the ease of use depends heavily whether the parameters passed can be intuitively distinguished by looking at there values. As well when the code is written first time as for maintaining it for the rest of your life.

(There are a bunch of other tricks to make parameters more readable, like using class constants as parameters, but this is out of scope of this article).

Filed on 31-07-2008, 01:01 under , , & 48 comments & no trackbacks

Trackbacks

Trackback specific URI for this entry

No Trackbacks

Comments

  1. Matthew Weier O'Phinney returns:
    published on July 31st 2008, 03:22:55 am *

    Reply

  2. Lars Strojny returns:
    published on July 31st 2008, 08:13:16 am *

    Reply

  3. Aaron reckons:
    published on July 31st 2008, 04:03:38 am *

    Reply

  4. Lars Strojny returns:
    published on July 31st 2008, 07:55:10 am *

    Reply

  5. Aaron Heimlich replys:
    published on July 31st 2008, 08:23:23 am *

    Reply

  6. Lars Strojny returns:
    published on July 31st 2008, 10:33:47 am *

    Reply

  7. Ivo states:
    published on July 31st 2008, 09:00:47 am *

    Reply

  8. Matthew Weier O'Phinney returns:
    published on July 31st 2008, 01:01:08 pm *

    Reply

  9. Joshua Trii means:
    published on July 31st 2008, 03:29:45 pm *

    Reply

  10. Thomas Koch says:
    published on July 31st 2008, 08:40:28 am *

    Reply

  11. Lars Strojny returns:
    published on July 31st 2008, 10:41:13 am *

    Reply

  12. Richard answers:
    published on July 31st 2008, 11:12:34 am *

    Reply

  13. Lars Strojny returns:
    published on July 31st 2008, 11:38:14 am *

    Reply

  14. tim answers:
    published on August 1st 2008, 02:32:13 am *

    Reply

  15. Lars Strojny returns:
    published on August 1st 2008, 08:44:20 am *

    Reply

  16. tim answers:
    published on August 1st 2008, 09:29:59 am *

    Reply

  17. Matthew Weier O'Phinney returns:
    published on August 1st 2008, 12:56:37 pm *

    Reply

  18. tim answers:
    published on August 1st 2008, 05:51:43 pm *

    Reply

  19. Joshua Trii means:
    published on August 1st 2008, 06:24:41 pm *

    Reply

  20. tim answers:
    published on August 1st 2008, 06:58:12 pm *

    Reply

  21. Lars Strojny returns:
    published on August 1st 2008, 08:12:19 pm *

    Reply

  22. tim answers:
    published on August 1st 2008, 11:10:50 pm *

    Reply

  23. Lars Strojny returns:
    published on August 1st 2008, 08:14:28 pm *

    Reply

  24. tim answers:
    published on August 1st 2008, 10:41:45 pm *

    Reply

  25. Matthew Weier O'Phinney returns:
    published on August 2nd 2008, 01:20:43 am *

    Reply

  26. Lars Strojny returns:
    published on August 2nd 2008, 02:23:08 am *

    Reply

  27. Dave responses:
    published on July 31st 2008, 11:03:01 am *

    Reply

  28. Lars Strojny returns:
    published on July 31st 2008, 11:35:37 am *

    Reply

  29. tim answers:
    published on August 1st 2008, 02:25:28 am *

    Reply

  30. Lars Strojny returns:
    published on August 1st 2008, 08:55:39 am *

    Reply

  31. Johannes answers:
    published on July 31st 2008, 11:59:37 am *

    Reply

  32. Lars Strojny returns:
    published on August 1st 2008, 08:46:28 am *

    Reply

  33. Dave responses:
    published on August 1st 2008, 06:26:01 pm *

    Reply

  34. Lars Strojny returns:
    published on August 2nd 2008, 02:27:12 am *

    Reply

  35. Lukas replys:
    published on July 31st 2008, 09:53:09 pm *

    Reply

  36. tinou answers:
    published on August 1st 2008, 09:04:26 am *

    Reply

  37. Derek means:
    published on August 1st 2008, 12:13:46 pm *

    Reply

  38. kira replys:
    published on August 1st 2008, 04:00:58 pm *

    Reply

  39. Lars Strojny returns:
    published on August 1st 2008, 08:15:40 pm *

    Reply

  40. MonkeyT reckons:
    published on August 2nd 2008, 02:37:24 am *

    Reply

  41. Lars Strojny returns:
    published on August 2nd 2008, 11:41:15 am *

    Reply

  42. stef responses:
    published on August 2nd 2008, 05:44:10 pm *

    Reply

  43. Lars Strojny returns:
    published on August 2nd 2008, 06:08:46 pm *

    Reply

  44. Koen states:
    published on August 3rd 2008, 01:01:04 am *

    Reply

  45. Lars Strojny opines:
    published on August 3rd 2008, 04:51:06 pm *

    Reply

  46. troels states:
    published on August 8th 2008, 12:31:21 am *

    Reply

  47. Koen states:
    published on August 8th 2008, 12:40:48 am *

    Reply

  48. Lars Strojny returns:
    published on August 8th 2008, 01:23:42 am *

    Reply

Add a Comment & let me know what you think

Submitted comments will be subject to moderation before being displayed.