Chapter 5. Abstract Factory

A lot of people love pizzas. The structure of a typical pizza is pretty much standard, even though it could be prepared in different pizzerias. A standard pizza has toppings, cheese, sauce, and dough. There are so many styles of pizzas out there. For example, New York-style and Chicago-style have different characteristics of ingredients; thin crust dough vs. thick crust dough, marinara vs. plum tomato sauce, and Reggiano vs. mozzarella cheese. Let's say you go to a pizzeria. There are two pizza chefs who are from New York and Chicago respectively. They specialize in their own pizza styles. This time you order a New York-style pepperoni pizza. Then back in the kitchen, the chef from New York has started preparing ingredients for your order, like thin crust dough, marinara sauce, Reggiano cheese, and some pepperoni. The next time, you go to the same pizzeria and order the same pizza but in Chicago style. Then the chef from Chicago will prepare stuff, like thick crust dough, plum tomato sauce, mozzarella cheese, and pepperoni. Although you order the same type of pizza (pepperoni), the characteristics of the ingredients are somewhat different from style to style.

Even though they seem to be different pizzas, the bottom line is they have the basic characteristics a pizza should have. So a "pizza" is like a type of food from a high-level perspective—or you can just simply call it an abstract food type. That abstract pizza type has some basic requirements, such as toppings, dough, cheese, and sauce, no matter what exactly they actually are. Pizza chefs are like factories that produce the same kind of product, but the style and attributes of the actual products could be different. All pizza chefs share pretty much the same "generic" or "abstract" knowledge of baking pizzas, though the final style of the pizza depends on the actual chef who prepared it. Those chefs from New York and Chicago are "actual" or "concrete" pizza chefs. They produce their pizzas in their specific styles. We consumers don't care about "how" the delicious pizzas are made, as long as they are good pizzas.

In software design, if a client wants to create an object of a class manually, then the client needs to know the details of the class in question to create it. What'd be worse is that a group of related objects can be created differently based on different criteria at runtime, and then the client needs to know all the nitty-gritty in order to create them. We can solve that problem by using the Abstract Factory pattern.

An abstract factory provides a consistent interface for creating families of related or dependent objects without specifying their concrete classes or any details about how to create them. The client is decoupled from any of the specifics of concrete objects obtained from a factory. Possible relationships among a family of factories and their products are illustrated in Figure 5-1.

A class diagram shows the relationships between a family of factories and their related products.

Figure 5-1. A class diagram shows the relationships between a family of factories and their related products.

As shown in Figure 5-1, the Client knows only AbstractFactory and AbstractProducts. The details of the structures and any actual operations are handled as a black box in each of the factory classes. Even the products have no idea who is going to be responsible for creating them. Only the concrete factories know how and what to create for the client. An interesting thing about this pattern is that a lot of times it's implemented with the Factory Method pattern (Chapter 5). A factory method defers the actual creation process to a subclass that overrides it. So in the diagram, the createProductA and createProductB| methods are factory methods. The original abstract methods don't create anything. This type of abstraction is so versatile and extensively used almost anywhere an abstract creation process is required. The Abstract Factory pattern is commonly used with other design patterns, such as the Prototype pattern (Chapter 3), the Singleton pattern (Chapter 7), and the Flyweight pattern (Chapter 21).

In this chapter, we are going to extend our TouchPainter app from Chapter 2 with the Abstract Factory pattern. Also we will discuss the pattern you will find commonly in the Cocoa Touch framework.

Note

ABSTRACT FACTORY: Provides an interface for creating families of related or dependent objects without specifying their concrete classes.[3]

Applying the Abstract Factory to the TouchPainter App

Let's picture a scenario: the companies you are partnering with would like to have the same TouchPainter app that we did in Chapter 2 under their brand names and logos. In this section, we are going to white-label and brand the app for two companies: Sierra Corporation and Acme Corporation. Their brands are Sierra and Acme.

Apparently, we didn't keep any branding in mind when we first designed the app. Now the best place to brand it is a main view that has some design specific to a particular brand. Other UI elements may also be involved in the branding process, such as a main button that takes the user to the main app as well as a toolbar on the main canvas. It seems there are quite a few variations we need to put into the architecture.

Note

THE GOLDEN RULE OF SOFTWARE DESIGN: Variation needs abstraction.

If you have multiple classes sharing common behavior but varying in actual implementations, then you may need to have some sort of abstraction type inherited as their parent. The abstraction type defines common behavior that all related concrete classes will share. For example, we know how a common pizza looks and what to expect when we order one. We say, "Let's go out for pizzas!"; "pizza" is an abstract type that defines common characteristics that a pizza should have. But we can get a slightly different style of the same, let's say, pepperoni pizza from different stores. Because there are so many different types of pizzas out there, we simply call them "pizzas" to refer to that particular type of food.

Back to our branding design, we have several variations in products and branding, so they need some sort of abstraction for all of them. We need two branding factories, and each one of them will produce three different UI elements by factory methods. There are customized UIView, UIButton, and UIToolbar elements for each brand. Their relationships are illustrated in a class diagram in Figure 5-2.

A class diagram of the TouchPainter app that supports multiple UI elements in different brands with the Abstract Factory pattern

Figure 5-2. A class diagram of the TouchPainter app that supports multiple UI elements in different brands with the Abstract Factory pattern

The class structure in Figure 5-2 is similar to the one in Figure 5-1. Each of the products has its own product hierarchy and is supported by a factory method (the Factory Method pattern, Chapter 4) in each concrete factory. Products are "manufactured" by two different branding factories, the SierraBrandingFactory and AcmeBrandingFactory. Each of them overrides the brandedView, brandedMainButton, and brandedToolbar factory methods declared in the abstract BrandingFactory class and returns concrete products based on the brand that the factory is designed for.

The superclass's class method factory is a factory method that returns a correct version of concrete BrandingFactory. Its subclasses are not supposed to override that method (even though subclasses are able to do that). The factory method returns an instance of a concrete branding factory based on the current build configuration. The default implementation of those factory methods (brandedView, brandedMainButton, and brandedToolbar) in the BrandingFactory returns instances of abstract products, UIView, UIButton, and UIToolbar, without any specifics to any branding. All the specifics required for each branding will be produced by an actual, concrete branding factory in its overridden factory methods.

In this setup, a client needs to know only four entities: BrandingFactory, UIView, UIButton, and UIToolbar. So in the future, if we need to expand the branding effort in other areas of the app, we can do so by adding new products and branding factories without affecting the client code.

Listing 5-1 illustrates how we can implement our branding factories and the corresponding products.

Example 5-1. BrandingFactory.h

@interface BrandingFactory : NSObject
{

}

+ (BrandingFactory *) factory;

- (UIView *) brandedView;
- (UIButton *) brandedMainButton;
- (UIToolbar *) brandedToolbar;

@end

Our no-frills, abstract BrandingFactory has a class method, + (BrandingFactory*) factory, which returns an instance of a concrete BrandingFactory subclass. It also defines three factory methods that produce instances of the actual products; brandedView returns an instance of UIView, brandedMainButton returns an instance of UIButton, and brandedToolbar returns an instance of UIToolbar. A concrete BrandingFactory will return actual products. We will get to that a little bit later. Let's take a look at the implementation of BrandingFactory in Listing 5-2.

Example 5-2. BrandingFactory.m

#import "BrandingFactory.h"
#import "AcmeBrandingFactory.h"
#import "SierraBrandingFactory.h"


@implementation BrandingFactory

+ (BrandingFactory *) factory
{
#if defined (USE_ACME)
  return [[[AcmeBrandingFactory alloc] init] autorelease];
#elif defined (USE_SIERRA)
 return [[[SierraBrandingFactory alloc] init] autorelease];
#else
 return nil;
#endif
}

- (UIView *) brandedView
{
  return nil;
}

- (UIButton *) brandedMainButton
{
  return nil;
}

- (UIToolbar *) brandedToolbar
{
  return nil;
}

@end

In the factory class method shown in Listing 5-2, we use pre-processor definitions to tell the compiler which concrete factory we want the method to return. If there is a macro definition to USE_ACME brand, then a statement, return [[[AcmeBrandingFactoryalloc] init] autorelease], will be used at runtime. If USE_SIERRA is defined in the macros, thena statement of return [[[SierraBrandingFactoryalloc] init] autorelease] will be used instead. Otherwise, the method will just return nil.

The original implementation for those factory methods returns nil by default. We are going to see how we can implement actual product creation by overriding those methods in concrete branding factories. The first one we are going to work on is a BrandingFactory for the Acme brand, as shown in Listing 5-3.

Example 5-3. AcmeBrandingFactory.h

#import "BrandingFactory.h"


@interface AcmeBrandingFactory : BrandingFactory
{

}

- (UIView *) brandedView;
- (UIButton *) brandedMainButton;
- (UIToolbar *) brandedToolbar;

@end

The AcmeBrandingFactory inherits the base class, BrandingFactory, and overrides its factory methods to produce correct UI elements for the brand. The original factory class method is not overridden, as it's meant to be used by the parent class to create an instance of concrete BrandingFactory. We cannot, though, prevent the class method getting overridden by any subclass. Let's see how we can implement AcmeBrandingFactory in Listing 5-4.

Example 5-4. AcmeBrandingFactory.m

#import "AcmeBrandingFactory.h"
#import "AcmeView.h"
#import "AcmeMainButton.h"
#import "AcmeToolbar.h"


@implementation AcmeBrandingFactory

- (UIView *) brandedView
{
  // returns a custom view for Acme
  return [[[AcmeView alloc] init] autorelease];
}

- (UIButton *) brandedMainButton
{
  // returns a custom main button for Acme
  return [[[AcmeMainButton alloc] init] autorelease];
}

- (UIToolbar *) brandedToolbar
{
  // returns a custom toolbar for Acme
  return [[[AcmeToolbar alloc] init] autorelease];
}

@end

Each implementation of a factory method returns an instance of Acme* product that reflects the actual brand. Listing 5-5 shows the implementation of another brand, Sierra.

Example 5-5. SierraBrandingFactory.h

#import "BrandingFactory.h"


@interface SierraBrandingFactory : BrandingFactory
{

}

- (UIView *) brandedView;
- (UIButton *) brandedMainButton;
- (UIToolbar  *) brandedToolbar;

@end

Its implementation is shown in Listing 5-6.

Example 5-6. SierraBrandingFactory.m

#import "SierraBrandingFactory.h"
#import "SierraView.h"
#import "SierraMainButton.h"
#import "SierraToolbar.h"

@implementation SierraBrandingFactory

- (UIView *) brandedView
{
  // returns a custom view for Sierra
return [[[SierraView alloc] init] autorelease];
}

- (UIButton *) brandedMainButton
{
  // returns a custom main button for Sierra
  return [[[SierraMainButton alloc] init] autorelease];
}

- (UIToolbar *) brandedToolbar
{
  // returns a custom toolbar for Sierra
  return [[[SierraToolbar alloc] init] autorelease];
}

@end

SierraBrandingFactory has almost the same implementation as the AcmeBrandingFactory except the names of the products that each of its factory methods returns are different. The returned UI elements are specific to the Sierra brand only. We can subclass the BrandingFactory to create a new brand and override new and existing factory methods to create any brand the same way as we do with Acme and Sierra.

Note

When an existing abstract factory needs to support new products, you need to add new corresponding factory methods to the parent class. That means all of its subclasses also need to be modified to support new factory methods for the new products.

Once a factory has returned some products, a client can use them like in Listing 5-7, in the loadView method of a view controller.

Example 5-7. loadView Method of a View Controller

// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
  // construct the view from
  // branded UI elements obtained
  // from a BrandingFactory
  BrandingFactory * factory = [BrandingFactory factory];

  //...
  UIView *view = [factory brandedView];
  //... put the view on a proper location in view

  //...
  UIButton *button = [factory brandedMainButton];
  //... put the button on a proper location in view

  //...
  UIToolbar *toolbar = [factory brandedToolbar];
  //... put the toolbar on a proper location in view
}

In the loadView method of a view controller, the controller can construct its view programmatically. In Listing 5-7, we ask the BrandingFactory to return an instance of an appropriate concrete BrandingFactory based on the current build configuration with its factory class method. Once we get a reference to an actual BrandingFactory, then we can call its factory methods to return UI elements so that we can construct any branded view elements programmatically.

A nice thing about using the class factory method to create an instance of concrete BrandingFactory here is that we don't need to design any complicated mechanism to determine what factory we should use at runtime. It's completely determined at compile time with some simple pre-processor definitions, and no other classes are involved in the process. Otherwise, it could be involved in more complicated changes in the app. The BrandingFactory is implemented as a form of class cluster in which a group of related subclasses are grouped together and created by a superclass. This kind of factory construction process is commonly found in the Foundation class library of the Cocoa Touch framework, which we are going to discuss in the next section.

Using the Abstract Factory Within the Cocoa Touch Framework

The Abstract Factory pattern is commonly seen in the Cocoa Touch framework. There are a lot of Foundation classes that have adopted the pattern. A particularly common one we use on a daily basis is NSNumber. The way in which we create an instance of NSNumber is where the picture of the Abstract Factory pattern is fitting right in.

There are two common ways to create Cocoa Touch objects: invoke the alloc then init methods (a two-step creation process) or a + className... method through a class. In the Foundation framework of the Cocoa Touch, the NSNumber class has numerous class methods to create different types of NSNumber objects, such as the following:

NSNumber * boolNumber = [NSNumber numberWithBool:YES];
NSNumber * charNumber = [NSNumber numberWithChar:'a'];
NSNumber * intNumber = [NSNumber numberWithInt:1];
NSNumber * floatNumber = [NSNumber numberWithFloat:1.0];
NSNumber * doubleNumber = [NSNumber numberWithDouble:1.0];

Each object returned belongs to a different private subclass that represents the original input value. Those class methods that create actual instances of NSNumber are similar to the factory method in the BrandingFactory described in the previous example. You can NSLog their class description as shown in the following:

NSLog(@"%@", [[boolNumber class] description]);
NSLog(@"%@", [[charNumber class] description]);
NSLog(@"%@", [[intNumber class] description]);
NSLog(@"%@", [[floatNumber class] description]);
NSLog(@"%@", [[doubleNumber class] description]);

You will see the following values in the Debugger Console output:

NSCFBoolean
NSCFNumber
NSCFNumber
NSCFNumber
NSCFNumber

Most of the actual classes are of NSCFNumber type, except the actual type for the boolNumber is NSCFBoolean. Although those + className class factory methods return instances of concrete subclasses of NSNumber, the returned instances do support the public interface of NSNumber.

Even though they are of different concrete subclasses of NSNumber, the behavior is defined by the abstract superclass NSNumber, and it is public. If you run the following code snippets, then you will understand what I mean.

NSLog(@"%d", [boolNumber intValue]);
NSLog(@"%@", [charNumber boolValue] ? @"YES" : @"NO");

You will see their output values in the Debugger Console as follows.

1
YES

boolNumber is maintaining a BOOL value YES internally, but it still implements the public intValue method to return a proper integer value that reflects its internal BOOL value. Same thing for charNumber—its overridden boolValue method returns an appropriate BOOL value to reflect its internal character value "a".

The class methods that take a parameter of a different type and return an instance of NSNumber are class factory methods (the Factory Method pattern, Chapter 4). The class factory methods in NSNumber produce different "number factories." numberWithBool: creates an instance of NSCFBoolean factory, while numberWithInt: creates an instance of NSCFNumber. Class factory methods in NSNumber define default behavior that determines what private, concrete subclasses (i.e., either NSCFBoolean or NSCFNumber) to instantiate. This version of factory method is a variant of the traditionally known Factory Method pattern, though it serves the purpose of returning abstract products, in this case, concrete NSNumber subclasses as factories. NSNumber is an example of Abstract Factory implementation. This flavor of Abstract Factory in the Foundation framework is referred to by the term "Class Clusters."

Class Clusters are a design pattern that is commonly found in the Foundation framework. They are based on the idea of the Abstract Factory pattern. They group a number of related, private, concrete factory subclasses under a public, abstract superclass. For example, "Number" contains a whole set of different numeric types, such as character, integer, floating-point number, and double. Those numeric types are a "subset" of "Number." So NSNumber naturally becomes a super-type of those number subtypes. NSNumber has a set of public APIs that defines common behavior shared by different types of numbers. Clients don't need to know the exact concrete type of an instance of NSNumber in order to use it.

Class Clusters are a form of Abstract Factory. As we can see, for example, NSNumber itself is a high-level abstract factory, while NSCFBoolean and NSCFNumber are concrete factory subclasses. The subclasses are concrete factories because they override public factory methods declared by NSNumber to produce products. For example, intValue and boolValue return values based on the internal value of an actual NSNumber object even though the data type of the value could be different. Actual values returned from those factory methods are "products" as described in the original definition of the Abstract Factory pattern.

There is a distinction between factory methods for creating abstract products and factory methods for creating abstract factories. Obviously, factory methods like intValue and boolValue should be overridden in concrete factories (NSCFNumber and NSCFBoolean) to return actual values (products). Other factory methods like numberWithBool: and numberWithInt: are not for returning products but for returning actual factories that return products. They are not intended to be overridden in concrete factory subclasses.

If you want to brew your own NSNumber, you can subclass it and override those defined class factory methods, like numberWithBool: and numberWithChar:, to return your own concrete subclasses instead of the built-in NSCFNumber and NSCFBoolean (in fact, Apple wouldn't expect us to use their private classes at all). Of course, your new NSNumber subclasses also need to implement factory methods, like intValue, boolValue, etc.

You might have already realized that the BrandingFactory is implemented as a form of class cluster. Clients don't need to know exactly what subclasses to use, but it's determined by the BrandingFactory's class factory method. The details of concrete branding subclasses are hidden from the view of clients, yet the subclasses implement the factory methods that return custom views, buttons, and toolbars declared in the public interface of BrandingFactory.

Other Foundation classes that were implemented as class clusters are NSData, NSArray, NSDictionary, and NSString.

Summary

The Abstract Factory pattern is one of the most commonly used design patterns. It is very fundamental, in that it can cover many types of object creation. A good pattern for a family of related classes should be hidden as a type of abstraction to the client. An abstract factory can provide this type of abstraction seamlessly without exposing any unnecessary details of the creation process or the exact type of object being created.

In the next chapter, we are going to look at another approach to creating abstract objects in a "Builder" way.



[3] 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
52.15.214.27