All OO languages have an idiom for object creation. However,
the core of any OOP is the management of the objects. Of course, the first step
of the object management is creating the object. In C#, this action is performed
using the new operator. In essence, the true important part
of managing an object is when to create the object.
As is well known, the types representing abstract definitions,
such as interfaces and abstract classes, can not be used to create their respective
instances. This means that the objects we want to create is relevant to concrete
object types. i.e. the creation of the object is sure to involve the detail of implementation,
which result in the close coupling between the creator and the concrete created
object. Let us take an example, in the next section, to look more closely at this
case.
Figure 1 - The initial idea in creating the objects
required in the graphic project
At the first face, the structure in Figure 1 appropriately tallies
with the OO idea. In terms of the polymorphism principle, we can substitute interface
IShape for the two concrete classes (in this case Square and Circle) in practical
programming, so that we defer the concrete object type binding to the running time.
However, due to the face that we can not directly create the interface, once the
IShape typed object requires to be created, we have to create the concrete class
related object, as shown below.
Listing 1
IShape shape=new Square();
Suppose that you are developing a graphic tool, there will be
lots of objects, such as Square and Circle, required to be created. Obviously, in
each of the modules in this sample project, with so many of the above lines stuffed
in it, it is very difficult for you to decouple the module from the Square object.
When we require to change the object to create to Circle, all the modules that invoke
the new Square() operation will have to be modified, which undoubtedly increases
the quantity of modification, as well as results in the inextensibility of the project
and the non-reusability of the modules. In this sense, the abstract interface IShape
concerning the above graphic objects has now become an unnecessary and failed design.
So, what should we do with the above case? Ideally, we would
kill this kind of diversification above in the cradle period. For this, we will
have recourse to the principle of "Encapsulate Variety." With this idea,
we can introduce the Factory Method pattern to encapsulate the creation of objects--the
IShape typed objects are the products the factory needs to create. In the Factory
Method pattern, the structure of the factory object should be in parallel with that
of the products. Well, since there are two types of products (i.e. the Square and
Circle), there also should be two corresponding concrete factory objects, SquareFactory
and CircleFactory. And also, should abstract a related interface IShapeFactory for
these two factories. Figure 2 indicates the relationships of these factory related
objects.
Figure 2 - The factory class structure introduced
by the Factory Method pattern
Below is the related code implementations.
Listing 2
public interface IShapeFactory
{
IShape CreateShape();
}
public class SquareFactory : IShapeFactory
{
public IShape CreateShape()
{
return new Square();
}
}
public class SquareFactory : IShapeFactory
{
public IShape CreateShape()
{
return new Square();
}
}
As is indicated above, with the help of the Factory Method pattern,
we have achieved the aim of encapsulating the creation of the objects, which shifts
all the code creating concrete products to the respective factory objects, which
is finally performed inside method CreateShape(). All these structures can be described
using the diagram in Figure 3.
Figure 3 - The whole structure of the Factory
Method pattern
Herein, the returned type of method CreateShape() is the type
of interface IShape, which efficiently avoids the dependency between the factory
objects and concrete product objects, decoupling the creator and the created object
in the end.
Digging Further
Is that the final result? No! In essence, although we have introduced
the Factory Method pattern in solving the above puzzles, we have not broken away
from the heavy payload resulted from the creation of concrete objects. By digging
further, it is not difficult to find out that although by introducing the Factory
Method pattern the task of creating the IShape object is transferred to the factory
object, the concrete factory object still exists inside the structure of the factory
class. That is to say, we have cut off the dependency between the modules and the
concrete graphic objects; however, this brings out the new dependency upon the concrete
factory objects. What benefits does this bring to us?
Let us continue to do some research from the point of view of
the creating frequency of the object. For a graphic tool, there is no doubt that
the IShape object is frequently created, one of the possibilities is that the IShape
object needs to be created in each module in the project. This, however, is not
the case for the factory object; we can initialize the factory object in a central
module and directly invoke the CreateShape method of the instance of the factory
object when in need of the IShape object.
For example, suppose that in the graphic tool there are three
modules, ModuleA, ModuleB, and ModuleC, all of which require creating the Square
object. In the light of the original design solution, each module should contain
the following:
Listing 3
IShape shape=new Square();
In this case, the above three modules all have dependency of
the concrete Square object, as is shown in Figure 4 below. Well, suppose you want
to change the shape object into the Circle type, this small alteration will surely
influences upon every corner of the project.
Figure 4 - All the three modules rely heavily upon
the graphic objects
Of course, we will have to continue to seek better
solutions. Now, by introducing the Factory Method pattern, we can add a new module
named ModuleFactory, inside which we create a factory object.
Listing 4
IShapeFactory shapeFactory=new SquareFactory();
With this new solution, ModuleA, ModuleB, and ModuleC should
also be altered accordingly.
Listing 5
IShape shape=new shapeFactory.CreateShape();
By now, the three original modules have broken away from the
dependency of concrete graphic objects; and also, this dependency has now been transferred
to the ModuleFactory module. Figure 5 shows the new relationships after
introducing the Factory Method pattern.
Figure 5
Well, when we are again in the face of the similar alteration
requirement (requiring modifying the creation of the shape object), it becomes
more feasible because the three modules (ModuleA, ModuleB, and ModuleC) are no
longer dependent of the concrete graphic objects. What we need to modify is the
parts (in this case the ModuleFactory module) that bear dependency of concrete
graphic objects. Please check out the following:
Listing 6
IShapeFactory sf=new CircleFactory();
By distinguishing Figure 4 from Figure 5, you can easily
find out that with the introduction of the Factory Method pattern the whole
design has been drastically improved. Despite that we have introduced three new
factory objects and the new ModuleFactory module; they together have decoupled the
three modules with concrete graphic objects, so that this dependency is shifted
into only one module -- ModuleFactory.
Altogether, introducing the factory object does not only mean
to build up the corresponding factory for each product, but to care more about
the compartmentalization of the responsibilities of different modules so as to put
the creation of the factory at the most proper site. Numerous design facts
prove that the best solution is to centralize the responsibility of creating
the factory object in a module, but not to create the factory objects just when
in need of create concrete products.