© Jennifer M. Kohnke
The man who builds a factory builds a temple.
—Calvin Coolidge (1872–1933)
The Dependency-Inversion Principle (DIP) (Chapter 11) tells us that we should prefer dependencies on abstract classes and avoid dependencies on concrete classes, especially when those classes are volatile. Therefore, the following snippet of code violates this principle:
Circle c = new Circle(origin, 1);
Circle
is a concrete class. Therefore, those modules that create instances of Circle
must violate DIP. Indeed, any line of code that uses the new
keyword violates DIP.
There are times when violating DIP is mostly harmless, which is pretty good coverage. The more likely a concrete class is to change, the more likely that depending on it will lead to trouble. But if the concrete class is not volatile, depending on it is not worrisome. For example, creating instances of string
does not bother me. Depending on string
is very safe because string
is not likely to change any time soon.
On the other hand, when we are actively developing an application, many concrete classes are very volatile, so depending on them is problematic. We’d rather depend on an abstract interface to shield us from the majority of the changes.
The FACTORY pattern allows us to create instances of concrete objects while depending only on abstract interfaces. Therefore, it can be of great assistance during active development when those concrete classes are highly volatile.
Figure 29-1 shows the problematic scenario. We have a class named SomeApp
that depends on the interface Shape
. SomeApp
uses instances of Shape
solely through the Shape
interface and does not use any of the specific methods of Square
or Circle
. Unfortunately, SomeApp
also creates instances of Square
and Circle
and thus has to depend on the concrete classes.
We can fix this by applying the FACTORY pattern to SomeApp
, as in Figure 29-2. Here, we see the ShapeFactory
interface, which has two methods: MakeSquare
and MakeCircle
. The MakeSquare
method returns an instance of a Square
, and the MakeCircle
method returns an instance of a Circle
. However, the return type of both functions is Shape
.
Listing 29-1 shows what the ShapeFactory
code looks like. Listing 29-2 shows ShapeFactoryImplementation
.
public interface ShapeFactory
{
Shape MakeCircle();
Shape MakeSquare();
}
Listing 29-2. ShapeFactoryImplementation.cs
public class ShapeFactoryImplementation : ShapeFactory
{
public Shape MakeCircle()
{
return new Circle();
}
public Shape MakeSquare()
{
return new Square();
}
}
Note that this completely solves the problem of depending on concrete classes. The application code no longer depends on Circle
or Square
, yet it still manages to create instances of them. It manipulates those instances through the Shape
interface and never invokes methods that are specific to Square
or Circle
.
The problem of depending on a concrete class has been moved. Someone must create ShapeFactoryImplementation
, but nobody else ever needs to create Square
or Circle
. ShapeFactoryImplementation
will most likely be created by Main
or an initialization function attached to Main
.
Astute readers will recognize a problem with this form of the FACTORY pattern. The class ShapeFactory
has a method for each of the derivatives of Shape
. This results in a name-only dependency that makes it difficult to add new derivatives to Shape
. Every time we add a new Shape
derivative, we have to add a new method to the ShapeFactory
interface. In most cases, this means that we’ll have to recompile and redeploy all the users of ShapeFactory
.1
We can get rid of this dependency problem by sacrificing a little type safety. Instead of giving ShapeFactory
one method for every Shape
derivative, we can give it only one make
function that takes a string
. For example, look at Listing 29-3. This technique requires that ShapeFactoryImplementation
use an if/else
chain on the incoming argument to select which derivative of Shape
to instantiate. This is shown in Listings 29-4 and 29-5.
Listing 29-3. A snippet that creates a circle
[Test]
public void TestCreateCircle()
{
Shape s = factory.Make("Circle");
Assert.IsTrue(s is Circle);
}
public interface ShapeFactory
{
Shape Make(string name);
}
Listing 29-5. ShapeFactoryImplementation.cs
public class ShapeFactoryImplementation : ShapeFactory
{
public Shape Make(string name)
{
if(name.Equals("Circle"))
return new Circle();
else if(name.Equals("Square"))
return new Square();
else
throw new Exception(
"ShapeFactory cannot create: {0}", name);
}
}
One might argue that this is dangerous, because callers who misspell the name of a shape will get a runtime error instead of a compile-time error. This is true. However, if you are writing the appropriate unit tests and are applying test-driven development, you’ll catch these runtime errors long before they become problems.
The trade-off that we’ve just witnessed between type safety and flexibility typifies the ongoing debate over language styles. On the one side are statically typed languages, such as C#, C++, and Java, which check types at compile time and emit compilation errors if the declared types aren’t consistent. On the other hand, dynamically typed languages, such as Python, Ruby, Groovy, and Smalltalk, do their type checking at runtime. The compiler does not insist on type consistence; nor, indeed, does the syntax of these languages permit such checking.
As we saw in the FACTORY example, static typing can lead to dependency knots that force modifications to source files for the sole purpose of maintaining type consistency. In our case, we have to change the ShapeFactory
interface whenever a new derivative of Shape
is added. These changes can force rebuilds and redeployments that would otherwise be unecessary. We solved that problem when we relaxed type safety and depended on our unit tests to catch type errors; we gained the flexibility to add new derivatives of Shape
without changing ShapeFactory
.
Proponents of statically typed languages contend that the compile-time safety is worth the minor dependency issues, the increased rate of source code modification, and the increased rate of rebuild and redeployment. The other side argues that unit tests will find most of the problems that static typing would find and that the burden of source code modification, rebuild, and redeployment is therefore unecessary.
I find it interesting that the rise in popularity of dynamically typed languages is, so far tracking the rise in adoption of test-driven development (TTD). Perhaps programmers who adopt TDD are finding that it changes the safety versus flexibility equation. Perhaps those programmers are gradually becoming convinced that the flexibility of dynamically typed languages outweighs the benefits of static type checking.
Perhaps we are at the crest of popularity of the statically typed languages. If the current trend continues, we may find that the next major industrial language is more related to Smalltalk than to C++.
One of the great benefits of using factories is the ability to substitute one implementation of a factory for another. In this way, you can substitute families of objects within an application.
For example, imagine an application that had to adapt to many different database implementations. In our example, let’s assume that the users can either use flat files or purchase an Oracle adapter. We might use the PROXY pattern to isolate the application from the database implementation.2 We might also use factories to instantiate the proxies. Figure 29-3 shows the structure.
Note the two implementations of EmployeeFactory
. One creates proxies that work with flat files, and the other creates proxies that work with Oracle. Note also that the application does not know or care which implementation is being used.
When writing unit tests, we often want to test the behavior of a module in isolation from the modules it uses. For example, we might have a Payroll
application that uses a database (see Figure 29-4). We may wish to test the function of the Payroll
module without using the database at all.
We can accomplish this by using an abstract interface for the database. One implementation of this abstract interface uses the real database. Another implementation is test code written to simulate the behavior of the database and to check that the database calls are being made correctly. Figure 29-5 shows the structure. The PayrollTest
module tests the PayrollModule
by making calls to it and also implements the Database
interface so that it can trap the calls that Payroll
makes to the database. This allows PayrollTest
to ensure that Payroll
is behaving properly. It also allows PayrollTest
to simulate many kinds of database failures and problems that are otherwise difficult to create. This is a testing pattern known as SELF-SHUNT, also sometimes known as mocking or spoofing.
How does Payroll
get the instance of PayrollTest
it uses as the Database
? Certainly, Payroll
isn’t going to do the creation of PayrollTest
. Just as clearly, Payroll
must somehow get a reference to the Database
implementation it’s going to use.
In some cases, it is perfectly natural for PayrollTest
to pass the Database
reference to Payroll
. In other cases, it may be that PayrollTest
must set a global variable to refer to the Database
. In still others, Payroll
may be fully expecting to create the Database
instance. In that last case, we can use a Factory
to fool Payroll
into creating the test version of the Database
by passing an alternative factory to Payroll
.
Figure 29-6 shows a possible structure. The Payroll
module acquires the factory through a global variable—or a static variable in a global class—named Gdatabase-Factory
. The PayrollTest
module implements DatabaseFactory
and sets a reference to itself into that GdatabaseFactory
. When Payroll
uses the factory to create a Database
, the PayrollTest
module traps the call and passes back a reference to itself. Thus, Payroll
is convinced that it has created the PayrollDatabase
, and yet the PayrollTest
module can fully spoof the Payroll
module and trap all database calls.
A strict interpretation of DIP would insist on using factories for every volatile class in the system. What’s more, the power of the FACTORY pattern is seductive. These two factors can sometimes lure developers into using factories by default. This is an extreme that I don’t recommend.
I don’t start out using factories. I put them into the system only when the need for them becomes great enough. For example, if it becomes necessary to use the PROXY pattern, it will probably become necessary to use a factory to create the persistent objects. Or, if through unit testing, I come across situations in which I must spoof the creator of an object, I will likely use a factory. But I don’t start out assuming that factories will be necessary.
Factories are a complexity that can often be avoided, especially in the early phases of an evolving design. When they are used by default, factories dramatically increase the difficulty of extending the design. In order to create a new class, one may have to create as many as four new classes: the two interface classes that represent the new class and its factory and the two concrete classes that implement those interfaces.
Factories are powerful tools. They can be of great benefit in conforming to DIP. They allow high-level policy modules to create instances of objects without depending on the concrete implementations of those objects. Factories also make it possible to swap in completely different families of implementations for a group of classes. However, factories are a complexity that can often be avoided. Using them by default is seldom the best course of action.
[GOF95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995.
34.238.242.168