Comments

02/11/06: Creational Patterns: The Factory Family

Here's one of my favorite rants: The Gang Of Four didn't do a very good job at laying out the "Creational Patterns". Their timing was all wrong: they jumped the gun and started with Abstract Factory. This reminds me of the following Steve Martin routine (paraphrased):
I was never good at dating. My timing was all wrong. I'd meet a girl in a bar and say, "Hi, was it good for you too?"

So here's my take, in proper order, on the progression of patterns for creating an object.

1. Constructor

new Foo(bar)
It should go without saying that the simplest and most straightforward way to create an object is to use its constructor. (Unfortunately, it often does not, and architects are too eager to open their UML modeling tools and forget how to spell n - e - w ...)

However, sometimes constructors are too limiting. Maybe there is some initialization that must happen before the object is constructed, or immediately after. Maybe you have lots of constructors with similar or identical parameter lists and you need a series of named methods to distinguish them. In that case, the next step is...

2. Factory Method

createFoo(bar)
createFooForSomeReason(bar)
createFooForSomeOtherReason(baz)
This is good when the caller has some logic around the object creation, either before or after. You may also want to override the Factory Method in a subclass, allowing polymorphic creation. See this article by Gopalan Suresh Raj for a thorough, if long-winded, discussion.

3. Static Factory Method

Foo.create(bar)
Foo.createForSomeReason(bar)
Foo.createForSomeOtherReason(baz)
If a factory method (#2) is generic enough, it often makes sense to put it as a static method on the object class. Usually if you do this you'll also want to make the constructor private, to force all callers to go through the factory method(s). This ensures consistent access to the object, allows you to apply the Complete Creation principle, and makes your code more readable, since the name of the factory method should describe the scenario (at least more than a bare "new" does).

This also allows your private constructor to become simpler. A factory method can take care of initialization stuff, and the constructor can take in already set-up objects and stick them in instance variables.

However, I am growing more and more reluctant to use statics (aka globals) of any kind. It seems to violate Separation of Concerns to put the code for *creating* an object in the same place as the code for *being* that object.

4. Factory Object

new FooFactory().create(bar)

Allows separation of concerns, and consistency between instances of the same class without relying on nasty static variables. Use this if constructing the factory itself is cheap.

Note that most OO languages, in moving the factory into a separate class you give up the power to access private members of the created class. This may mean you have to refactor the target object to expose a public (or package- or friend-accessible) method to do initialization.

It's a simple step from here to #6, a Dependency-Injected Singleton factory.

5. Static Singleton Factory Object

FooFactory.getInstance().create()

Using a singleton gives you a common entry point for creating objects across your application. That means you can do tricks like caching, instance pooling, referential integrity checking and maintenance, etc.

Static singletons like this are easy to use since you can always just reach out and find one. But this is not ideal cause you need static registry and teardown semantics, and it's hard (OK, annoying) to swap out instances at runtime, for instance for tests, without compromising your ability to do multithreading. Dependency Injection 0wnz0rrz statics of all types (including this one) (see below).

6. Injected Singleton Factory Object

getFooFactory().create()

I might get in trouble for calling this a Singleton. The basic idea behind Dependency Injection that when you want something -- any service, like a factory, a repository, a search server, an asynchronous message scheduler -- you just ask yourself for it. You have it already, since in your constructor your client passed it in to you, and you stashed it away in an instance variable.

This allows the oxymoronic "multiple singletons" pattern: at any given point there's only one of the things in scope, but there can be many in the same application or VM; in fact, many different instances can be active at the same time. This is great for testability and flexibility.

7. Abstract Factory

Abstract Factories are aware of the whole hierarchy of possible subclasses of the target object and return the right one for the given use at runtime. A better name might be "polymorphic factory."

It's a pretty heavyweight pattern and a long way from "new". You may never use it. I never have.

Comments made

"However, I am growing more and more reluctant to use statics (aka globals) of any kind. It seems to violate Separation of Concerns to put the code for *creating* an object in the same place as the code for *being* that object."

I'm not convinced of this. For me it's simply an organizational problem - do you want to make me jump off somewhere else, or can I just see it inline with the class it relates to?

Also I find that I just don't care as much about stuff like this in Ruby, there are typically so few lines of very readable code anyway that putting a couple of "def self.foo" methods elsewhere is just confusing.

Also of note, if you have the separate factory class and the resulting object could have an arbitrary combination of configuration features, with "return this" you can do something like:

SomeClass myobj =
new Factory
.pickUpAfterTheDog()
.waterTheLawn()
.dontLeaveTheToiletSeatUp()
.create();

Of course in ruby you can just pass in a hash in the constructor with symbol/value pairs for each of these things...
02/14/06 07:48:02
01/21/17 00:44:06

Add comment

Sorry, but due to blog comment spam, I have to ask you to create an account before you post a comment. Please log in (using the form on the top right of the page) or click here to create an account: Create an account!