A Factory Should Create, Not Retain
In a recent Reddit conversation, some of us went off on a tangent about factories. I maintained then, and do now, that a “factory” always-and-only returns a new instance. If you have a “factory” that returns anything other than a new instance, it’s not a factory alone. In the case of factory methods, it is a factory + accessor; in the case of factory objects, it is a factory + registry.
A “factory” (whether a factory method or factory object) is one way to separate object creation from object use.
Let’s say we need to create an object to do some work in a class method:
class Example
{
public function __construct($db)
{
$this->db = $db;
}
public function doSomething($itemId)
{
$data = $this->db->fetchOne(
"SELECT * FROM items WHERE id = :id",
['id' => $itemId]
);
$item = new Item($data);
// do some other work with the item,
// the return the results of that work.
return $results;
}
}
The item creation is mixed in with the use of the item. What we want to do is separate the creation from the usage. One way to do that is to use a factory method, or a factory object, to handle object creation for us. Here’s an example of a factory method:
class Example
{
public function __construct($db)
{
$this->db = $db;
}
public function doSomething($itemId)
{
$data = $this->db->fetchOne(
"SELECT * FROM items WHERE id = ?",
['id' => $id]
);
$item = $this->item($itemId);
// do some other work with the item,
// the return the results of that work.
return $results;
}
public function item($data) // factory method
{
return new Item($data);
}
}
Here’s an example of an injected factory object:
class Example
{
public function __construct($db, $factory)
{
$this->db = $db;
$this->factory = $factory;
}
public function doSomethingWithItem($id)
{
$data = $this->db->fetchOne(
"SELECT * FROM items WHERE id = ?",
['id' => $id]
);
$item = $this->factory->item($data);
// do some other work with the item,
// the return the results of that work.
return $results;
}
}
class Factory // factory object
{
public function item($data) // factory method
{
return new Item($data);
}
}
There were some folks in the Reddit thread that believe a factory may additionally retain the created instance for reuse. For example, let’s say we create and the reuse a collaborator object of some sort:
class Example
{
public function doSomething()
{
$collab = $this->collab();
// do stuff, then:
return $result;
}
protected function collab() // factory method?
{
if (! $this->collab) {
$this->collab = new Collab();
}
return $this->collab;
}
}
As far as I can tell, that’s not just a factory. There are three things going on there: initializing a property, creating an object, and accessing a property. To split the concerns, one would need two methods:
class Example
{
public function doSomething()
{
$collab = $this->getCollab();
// do stuff, then:
return $result;
}
protected function getCollab() // initialize + access
{
if (! $this->collab) {
$this->collab = $this->newCollab();
}
return $this->collab;
}
protected function newCollab() // factory
{
return new Collab();
}
}
Now the concern of creating the object is represented by newCollab()
method, and the concerns of initializing and accessing the property are represented by the getCollab()
method. Indeed, splitting out a factory method in this kind of situation is the exact recommendation in the GOF Design Patterns book on page 113; this is the book that defined the term "factory method" in the first place, so it seems authoritative on this point.
What happens when you add retention to what would otherwise be a factory object? For example, is the following only a factory object, or does it do more than just create and return new instances?
class WhatIsThatThing
{
public function getService()
{
if (! $this->service) {
$this->service = $this->newService();
}
return $this->service;
}
public function newService()
{
return new Service();
}
}
It both creates objects, and retains the created objects. That makes it more than just a factory; it looks more like a container at this point.
Finally, regarding method names, it seems like it’s best to indicate what to expect as a return result. I default to newInstance()
in factory objects that deal with only a single class, and new<Type>()
when the factory object creates many different classes. Using get<Type>()
indicates that a pre-existing instance (probably memoized in a property) is being returned.
Read the Reddit discussion about this post here.