Chapter 4. Factory Method

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.

What Is the Factory Method Pattern?

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.

A class diagram of the Factory Method pattern

Figure 4-1. A class diagram of the Factory Method pattern

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.

Note

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.

When Would You Use the Factory Method?

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.

Why Is It a Safe Approach to Creating Objects?

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.

Generating Different Canvases in TouchPainter

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.

A class diagram illustrates a parallel class hierarchy of ClothCanvasView, PaperCanvasView, and their corresponding generators.

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

Using Canvases

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 CanvasViewGenerator
  CanvasViewGenerator *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.

Using Factory Methods Within the Cocoa Touch Framework

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.

Summary

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).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.41.212