We have seen factory methods almost in every application written in an object-oriented language. The Factory Method pattern is an integral part of the Abstract Factory pattern in Chapter 5. Different concrete factories create their own products (objects) by using an overridden factory method that was defined in a parent abstract factory class.
An object factory is like real factories that manufacture tangible products; for example, a shoe manufacturer produces shoes and a cell phone manufacturer produces cell phones. Let's say you are asking the manufacturers to produce some products for you, and you send them a "produce product" message. Both the shoe and cell phone manufacturers are following the same protocol of "produce product" to start their production lines. At the end of the process, each manufacturer returns the specific type of products they produced. We call the magic word "produce" a factory method because it's a method to tell a creator (factory) to get what you want.
The creator itself may not necessarily be an abstract factory; it could be any class. The whole point is not to create objects directly but to use a factory method of a class/object to create concrete products for you that are returned as an abstract type. Why is it better? We are going to discuss it in more detail in the next sections.
A factory method is also called a virtual constructor. It's applicable when a class can't anticipate the class of objects it must create and wants its subclasses to specify the objects it creates.
A static class structure of the Factory Method pattern is illustrated in Figure 4-1.
The abstract Product
defines the interface of objects the factory method creates. The ConcreteProduct
implements the Product
interface. The Creator
defines the factory method that returns an object of Product
. It may also define a default implementation of the factory method that returns a default ConcreteProduct
object. Other operations of the Creator
may call the factory method to create a Product object. The ConcreteCreator
is a subclass of the Creator
. It overrides the factory method to return an instance of ConcreteProduct
.
THE FACTORY METHOD PATTERN: Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.[2]
The original definition of the Factory Method pattern seems to focus on letting subclasses decide what objects to create. There is a variant that an abstract class uses a factory method to create objects of its private subclasses or any other classes. We will discuss this type of variant later in this chapter.
You'd naturally think about using the Factory Method pattern when
The exact class of objects created can't be anticipated at compile time.
A class wants its subclasses to decide what to create at runtime.
A class has some helper classes as its subclasses and you want to localize the knowledge of which one to return.
The bottom line of using this pattern is that a factory method can give a class more flexibility to change what class of objects to return. A common example of using this architecture is NSNumber
in the Cocoa Touch framework (or Cocoa in general). Even though you can create an instance of NSNumber
with a typical allocinit
two-step approach, it doesn't do much unless you use one of its predefined class factory methods to create a meaningful instance of it. For example, a message [NSNumbernumberWithBool:YES]
will get you an instance of NSNumber
's subclass (NSCFBoolean
) that contains the Boolean value provided to the class factory method. The Factory Method pattern is particularly useful for framework designers. We will discuss a little more about factory methods in the Cocoa Touch framework in a later section.
It's considered a best practice to create objects with a factory method as opposed to creating new concrete objects directly. The Factory Method pattern allows clients to expect that objects that are created by a factory method share a common set of behavior. So any new introduction of new concrete products to the class hierarchy doesn't need any code changes to the client code, because the interface of any returned concrete object is the same as the old ones that the clients have been using.
For the TouchPainter app that is discussed in Chapter 2, we needed to have a canvas view to let the user draw something with a finger. That canvas view incorporates with a drawing algorithm to render any scribble produced by touches (see the Composite pattern in Chapter 13 and the Visitor pattern in Chapter 15). The canvas class is called CanvasView
, which is a subclass of UIView
. We are going to use that class as a top-level abstract class and extend it with a couple more subclasses to provide more canvas types for the user. There will be two more canvas types to choose from, ClothCanvasView
and PaperCanvasView
. ClothCanvasView
will have a cloth-style background, while PaperCanvasView
will have a recycled paper-style background. Each of them would have some more behavior specific to the canvas. However, we will not go over the details of them; we will focus on using the pattern to generate them at runtime.
We know we are going to add two more canvas types to the original CanvasView
. But we are not going to instantiate either one of them directly. Otherwise, every time we need to add more canvas types or even just to modify their initialization interfaces, we also need to modify the client code to reflect the changes. Instead we will let the client use some top-level classes for both CanvasView
and CanvasViewGenerator
to decouple with any specific CanvasView
that will be returned.
An instance of a specific CanvasView
is created by a factory method, canvasViewWithFrame:aFrame
, defined in CanvasViewGenerator
abstract class. There are two subclasses of CanvasViewGenerator
. Each of them is responsible for creating an instance of a specific CanvasView
by overriding the parent's canvasViewWithFrame:
method. PaperCanvasViewGenerator
will create an instance of PaperCanvasView
while ClothCanvasViewGenerator
will create an instance of ClothCanvasView
. Their relationships are illustrated in Figure 4-2.
Figure 4-2. A class diagram illustrates a parallel class hierarchy of ClothCanvasView
, PaperCanvasView
, and their corresponding generators.
The controller of the CanvasView
, CanvasViewController
, will be the client of instances of both CanvasView
and CanvasViewGenerator
. The CanvasViewController
will use an instance of CanvasViewGenerator
to get a reference to a correct instance of CanvasView
based on the user's preferences. Different types of CanvasViewGenerator
can be used to generate different canvases for the CanvasViewController
.
The top-level CanvasView
defines what the default behavior of any CanvasView
type should be. Subclasses of it present different textures with different images on screen and other possible specific behaviors. Code snippets for both the PaperCanvasView
and ClothCanvasView
are illustrated in Listing 4-1.
Example 4-1. PaperCanvasView.h
#import <UIKit/UIKit.h> #import "CanvasView.h" @interface PaperCanvasView : CanvasView { // some private variables } // some other specific behaviors @end
Listing 4-1 shows the class declaration of PaperCanvasView
with some omitted behavior and private member variables. Let's assume one of its particular behaviors is it has its own image view that has a paper texture image, as in Listing 4-2.
Example 4-2. PaperCanvasView.m
#import "PaperCanvasView.h" @implementation PaperCanvasView
- (id)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { // Add a paper image view on top // as the canvas background UIImage *backgroundImage = [UIImage imageNamed:@"paper"]; UIImageView *backgroundView = [[[UIImageView alloc] initWithImage:backgroundImage] autorelease]; [self addSubview:backgroundView]; } return self; } // implementation for other behaviors @end
@"paper"
is the name of the image that has the texture of paper. It's assigned to backgroundView
, which is an instance of UIImageView
. Then the backgroundView
is added to the main content view as a subview to show the paper image. Likewise, we have a declaration for ClothCanvasView
as in Listing 4-3.
Example 4-3. ClothCanvasView.h
#import <UIKit/UIKit.h> #import "CanvasView.h" @interface ClothCanvasView : CanvasView { // some private variables } // some other specific behaviors @end
Like PaperCanvasView
, we have omitted some possible specific behaviors of ClothCanvasView
here for brevity. ClothCanvasView
also displays a different image texture as part of its unique behavior, as shown in Listing 4-4. Its texture image is called @"cloth"
and is constructed almost the same way as in PaperCanvasView
in Listing 4-2.
Example 4-4. ClothCanvasView.m
#import "ClothCanvasView.h" @implementation ClothCanvasView - (id)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { // Add a cloth image view on top
// as the canvas background UIImage *backgroundImage = [UIImage imageNamed:@"cloth"]; UIImageView *backgroundView = [[[UIImageView alloc] initWithImage:backgroundImage] autorelease]; [self addSubview:backgroundView]; } return self; } // implementation for other behaviors @end
Implementations for both PaperCanvasView
and ClothCanvasView
are very straightforward. They inherit the default behavior defined in the CanvasView
class as well as possibly some other ones. Now we have our products defined. We also need to define a creator for each of them. The following code snippets demostrate the implementation of our abstract CanvasViewGenerator
class in Listings 4-5 and 4-6.:
Example 4-5. CanvasViewGenerator.h
#import "CanvasView.h" @interface CanvasViewGenerator : NSObject { } - (CanvasView *) canvasViewWithFrame:(CGRect) aFrame; @end
Example 4-6. CanvasViewGenerator.m
#import "CanvasViewGenerator.h" @implementation CanvasViewGenerator - (CanvasView *) canvasViewWithFrame:(CGRect) aFrame { return [[[CanvasView alloc] initWithFrame:aFrame] autorelease]; } @end
CanvasViewGenerator
has one method, canvasViewWithFrame:(CGRect) aFrame
. The default implementation of that method is just to create an instance of plain CanvasView
and return it. Subclasses of this generator need to override this method to return the actual concrete type of CanvasView
, such as PaperCanvasViewGenerator
, as shown in code snippets in Listings 4-7 and 4-8.:
Example 4-7. PaperCanvasViewGenerator.h
#import "CanvasViewGenerator.h" #import "PaperCanvasView.h"
@interface PaperCanvasViewGenerator : CanvasViewGenerator { } - (CanvasView *) canvasViewWithFrame:(CGRect) aFrame; @end
Example 4-8. PaperCanvasViewGenerator.m
#import "PaperCanvasViewGenerator.h" @implementation PaperCanvasViewGenerator - (CanvasView *) canvasViewWithFrame:(CGRect) aFrame { return [[[PaperCanvasView alloc] initWithFrame:aFrame] autorelease]; } @end
The PaperCanvasViewGenerator
overrides the canvasViewWithFrame:
method to return an instance of PaperCanvasView
.
Another generator that returns an instance of ClothCanvasView
is defined in Listings 4-9 and 4-10.
Example 4-9. ClothCanvasViewGenerator.h
#import "CanvasViewGenerator.h" #import "ClothCanvasView.h" @interface ClothCanvasViewGenerator : CanvasViewGenerator { } - (CanvasView *) canvasViewWithFrame:(CGRect) aFrame; @end
Example 4-10. ClothCanvasViewGenerator.m
#import "ClothCanvasViewGenerator.h" @implementation ClothCanvasViewGenerator - (CanvasView *) canvasViewWithFrame:(CGRect) aFrame { return [[[ClothCanvasView alloc] initWithFrame:aFrame] autorelease]; } @end
The CanvasViewController
is using the original CanvasView
. In order to change that at runtime, we need to add a method in the CanvasViewController
that uses a CanvasViewGenerator
to get an instance of CanvasView
, as shown Listing 4-11.
Example 4-11. CanvasViewController.h
#import "CanvasView.h"
#import "CanvasViewGenerator.h"
@interface CanvasViewController : UIViewController
{
@private
CanvasView *canvasView; // a canvas view
}
@property (nonatomic, retain) CanvasView *canvasView;
- (void) loadCanvasViewWithGenerator:(CanvasViewGenerator *)generator;
@end
CanvasViewController
has a new method called loadCanvasViewWithGenerator:(CanvasViewGenerator *)generator
that takes an instance of CanvasViewGenerator
and asks it to return an instance of CanvasView
to be used in the controller. The implementation of this method is shown in Listing 4-12.
Example 4-12. CanvasViewController.m
#import "CanvasViewController.h" @implementation CanvasViewController @synthesize canvasView=canvasView_; // Implement viewDidLoad to do additional setup after loading the view, // typically from a nib. - (void)viewDidLoad { [super viewDidLoad]; // Get a default canvas view // with the factory method of // the CanvasViewGeneratorCanvasViewGenerator *defaultGenerator = [[[CanvasViewGenerator alloc] init]
autorelease];
[self loadCanvasViewWithGenerator:defaultGenerator];
} // Unrelated methods are removed for the sake of brevity #pragma mark - #pragma mark Loading a CanvasView from a CanvasViewGenerator- (void) loadCanvasViewWithGenerator:(CanvasViewGenerator*)generator
{
[canvasView_ removeFromSuperview];
CGRect aFrame = CGRectMake(0, 0, 320, 436);
CanvasView *aCanvasView = [generator canvasViewWithFrame:aFrame];
[self setCanvasView:aCanvasView];
[[self view] addSubview:canvasView_];
}
@end
First we ask canvasView_
(a member variable that holds the current instance of CanvasView
) to remove itself from its superview before we add a new one to it. Then we send a message of canvasViewWithFrame:aFrame
with a specified frame size to generator
for an instance of CanvasView
. Based on the type of the generator, an appropriate instance of CanvasView
is returned. We assign a newly returned aCanvasView
to the member variable canvasView_
through an accessor method setCanvasView:
. Why? It's because the returned aCanvasView
is autoreleased and we want to retain it in the controller. It's a good practice to set any member variable using an accessor method rather than sending a retain
message manually. retain
is defined as an attribute of the canvasView
property, so we are good once aCanvasView
is set with the property. Then we add the updated canvasView_
(now it's aCanvasView
) back to the controller's main view as a new subview.
So next time around, when the user selects a specific canvas type, the app will pass an instance of a concrete generator to the loadCanvasViewWithGenerator:
method and the original canvas view will be replaced by a new one.
Factory methods can be seen almost everywhere in the Cocoa Touch framework. We already know the common two-step object creation: [[SomeClassalloc] init]
. Sometimes, we have noticed that there are some "convenience" methods that return an instance of the class. For example, NSNumber
has numerous numberWith*
methods; two of them are numberWithBool:
and numberWithChar:
. They are class methods, which means that you send [NSNumbernumberWithBool:bool]
and [NSNumbernumberWithChar:char]
to NSNumber
to get an instance of NSNumber
with different types you pass in as a parameter. The class factory methods of NSNumber
take care of all the details about how to create an instance of a concrete subtype of NSNumber
. In the case of [NSNumbernumberWithBool:bool]
, the method takes the bool
value and initializes an instance of internal subclass of NSNumber
that can reflect the provided bool
value.
We have mentioned that there is a variant of the Factory Method pattern that is used for generating concrete subclasses by an abstract class. Those numberWith*
methods in NSNumber
are an example of the variant. They are not meant to be overridden by private subclasses of NSNumber
but are convenient ways for NSNumber
to create whatever is appropriate. In our foregoing TouchPainter app example, an instance of CanvasViewGenerator
needs to be created by some other classes. For the case of NSNumber
, there are no other "number generators" to be generated elsewhere but methods that are bundled at the class level that serve a similar purpose. They are called class factory methods.
The Factory Method pattern we implemented in the TouchPainter example can be simplified with this type of variant by bundling a group of class factory methods to return different types of concrete CanvasView
. The only problem is that the client (CanvasViewController
) now needs to be aware of what exactly it wants (with exact class factory methods) but not a generator selected and returned by the user.
The Factory Method pattern is one of the most commonly used design patterns in object-oriented software design. Factory methods decouple application-specific classes from your code. The code only needs to deal with any abstract Product
interface. So the same code can be reused to work with any user-defined ConcreteProduct
classes in the application. We have used the Factory Method pattern to help us implement the TouchPainter app to support multiple types of CanvasView
that would be selectable by the user.
In the next chapter, we are going to see another pattern for object creation that is closely related to the Factory Method pattern. Their resemblance is always confusing.
[2] The original definition appeared in Design Patterns, by the "Gang of Four" (Addison-Wesley, 1994).
3.139.97.202