Question

Currently, I use an abstract factory to allow the specification of a custom class name for generating a request object. My reasoning for doing this is to allow me to easily extend core functionality without altering code. Recently, though, I've had some doubts as to the efficacy of this approach. So my question is this:

Is allowing the factory to instantiate any submitted class name that matches the expected interface a bastardization of the factory concept? Would I be better served to avoid this?

UPDATE

The logic here is this: on the one hand, a real-life car factory (for example) can't create a car if it's not equipped with the machinery to make that kind of car. On the other hand, the code below is like giving that same car factory the blueprint to make the custom car it wasn't originally intended to build.

An alternative would be to pass in a configuration object specifying a custom class name that may be used with the factory and limit the factory to producing a custom class only if it specifically matches the config-specified custom class name. Any thoughts?


And the relevant code ...

<?php

interface AbstractRequestFactory
{
  public function buildRequest($type);
}

class RequestFactory implements AbstractRequestFactory
{
  public function buildRequest($type='http')
  {
    if ($type == 'http') {
      return new HttpRequest();
    } elseif ($type == 'cli') {
      return new CliRequest();
    } elseif ($custom = $this->makeCustom($type)){
      return $custom;
    } else {
      throw new Exception("Invalid request type: $type");
    }
  }

  protected function makeCustom($type)
  {
    if (class_exists($type, FALSE)) {
      $custom = new $type;
      return $custom instanceof RequestInterface ? $custom : FALSE;
    } else {
      return FALSE;
    }
  }
}

// so using the factory to create a custom request would look like this:

class SpecialRequest implements RequestInterface {}

$factory = new RequestFactory();
$request = $factory->buildRequest('\SpecialRequest');
Was it helpful?

Solution

What you have looks pretty good. The point of having a factory is to pass in some criteria, and have the method return you back an object, which you assume will have the same callable methods available to the calling code. You are enforcing this assumption by implementing the RequestInterface, so as long as any custom request classes implement the same interface, you won't end up in a 'unable to call function on non-object' scenario.

A couple of recommendations (just personal preference):

  • I would use switch / case on $type in buildRequest

  • I would return null or object from makeCustom(), otherwise you are mixing return types (object and bool)

  • Depending on how many custom types you have, I would actually hard code them into the switch case, just to alleviate any confusion. Don't get me wrong, what you have is great if you have a lot of classes, but chances are you don't.

  • Did you ever consider putting the "easily extend core functionality without altering code" piece into an abstract parent class, which can be extended by custom type classes?

  • Also, because a factory creates objects, it's common practice to set it as static.

Example code snippet:

public static function getRequest($type='http')
{
    switch ($type) {

        case 'http':
            return new HttpRequest();

        case 'cli':
            return new CliRequest();

        case 'myCustom1':
            return new MyCustom1();

        case 'myCustom2':
            return new MyCustom2();

        default: 
            throw new Exception("Invalid request type: $type");
    }
}

$request = RequestFactory::getRequest($type);

// As long as all objects in factory have access to same methods
$request->doSomething();
$request->andDoSomethingElse();

// Otherwise you end up with that feared 'unable to call function on non-object'
$request->iAmASneakyMethodNotEnforcedByAnInterfaceOrAvailableByExtension();    

OTHER TIPS

This is pretty subjective, so the following is just one opinion:

I wouldn't be quick to use something like this. If you only have a handful of classes that the factory will ever care about, then I'd just hard code them. But if you have a large set of these, I think it can be appropriate.

Given that you are validating that the class extends the appropriate interface, I would say that there is nothing wrong with what you are doing because it is fail-safe. The code using that factory method will appear clean; I think that's the most important thing.

If you were making use of such techniques all over the place, then I would argue against it. But since this is hidden away in the implementation, I think you can have more leeway with doing slightly inappropriate things.

Why not use a dispatch array? i.e.

class RequestFactory 
{
    private static $requests = array(
      'http' => 'HttpRequest',
      'cli' => 'CliRequest',
      'summatelse' => 'Summat'
    );
    public static GetRequest($type)
    {
       if (array_key_exists($type, $requests)) return new $requests[$type];
       else throw new Exception("Invalid request type: $type"); 
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top