Chapter 18. Template Method

As in cooking, there are certain steps that can be generalized in common procedures, such as preparing ingredients, cooking the ingredients, and serving the dish. An actual recipe can vary within the scope of each of the steps. For a particular type of food, steps to take can be even further generalized. For example, there are common steps to make a sandwich, no matter what type it is. Each type of sandwich can vary by the type of bread that will be used, the type of meat that will be put in the bread, as well as condiments. Some sandwiches might be a little bit different from a generic sandwich, with some extra steps, but still basically follow the common steps to prepare.

A group of generalized steps (a recipe) to prepare a sandwich is a template method. It's a template because it's still missing some specific pieces in the steps to complete the procedure. Those missing pieces will be filled up when a particular type of sandwich needs to be made. When we prepare a Reuben sandwich, we need to prepare a couple slices of rye bread in a "prepare bread" step. A hamburger requires hamburger buns in the same step. No matter what type of sandwich it is, it still needs "bread" to complete the process. So we can group those generalized steps into a single operation called "make a sandwich." A specific type of sandwich recipe fills in some empty steps with unique procedures and ingredients.

In this chapter, we will look at the concepts of the Template Method pattern. We will also implement the pattern with an example of preparing different kinds of sandwiches with a template "recipe."

What Is the Template Method Pattern?

The Template Method pattern is one of the simplest forms of design patterns in object-oriented software design. The basic idea is to define a "standard" algorithm in a method of an abstract class. Within the method, it will call other primitive operations that are supposed to be overridden by subclasses. The method is called "template" because the method defines the algorithm in which some unique operations are missing. The relationship between an abstract class that defines a template method and a concrete subclass that overrides primitive operations to provide unique operations when those methods are called in the template method is illustrated in Figure 18-1.

A class diagram of the Template Method pattern that shows ConcreteClass overrides AbstractClass's primitiveOperation1 and primitiveOperation2 to provide unique operations when Client invokes templateMethod in AbstractClass

Figure 18-1. A class diagram of the Template Method pattern that shows ConcreteClass overrides AbstractClass's primitiveOperation1 and primitiveOperation2 to provide unique operations when Client invokes templateMethod in AbstractClass

AbstractClass partially defines some methods and algorithms with some operations left out. When ConcreteClass overrides primitiveOperation1 (and/or 2), it fills up the "gaps" of templateMethod in AbstractClass when clients invoke the method.

Note

THE TEMPLATE METHOD PATTERN: Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.[16]

When Would You Use the Template Method?

You should consider using the Template Method pattern when

  • You need to implement the invariant parts of an algorithm once and leave it up to subclasses to implement any specific behavior that can vary.

  • Common behavior of subclasses should be factored in a common class to avoid code duplication. Differences in existing code should be separated into new operations. Then you replace the differing code with a template method that calls each of these new operations.

  • Controlled subclass extensions are required. You can define a template method that calls "hook" operations at specific points. Subclasses can extend the functionality at those points with the hook implementation.

A hook operation provides default behavior that subclasses can extend. It often does nothing by default. Subclasses can override that method to provide extra operations for the template algorithm.

The flow of control structure in the Template Method pattern is inverted, as a template method of a parent class calls operations of its subclass and not the other way around. It's analogous to "the Hollywood principle": don't call us, we'll call you.

There are five types of operations that template methods would call:

  • Concrete operations either on concrete classes or client classes

  • Concrete operations on abstract classes

  • Abstract operations

  • Factory methods (see Chapter 4)

  • Hook operations (optional abstract operations)

Using the Template Method to Make a Sandwich

Let's take a look at making a sandwich and see how it can be related to the Template Method pattern.

I think anyone can make a simple sandwich. What should a (non-vegetarian) sandwich have to be considered a sandwich?

  • Bread

  • Meat

  • Condiments

So we have all the ingredients to make a simple sandwich. Then a basic procedure to make a simple sandwich should be somewhat like the following:

  1. Prepare the bread.

  2. Put the bread on a plate.

  3. Add meat on the bread.

  4. Add condiments.

  5. Serve.

We can define a template method called make in which other specific steps like the foregoing ones are called to make a real sandwich. The default algorithm to make real sandwiches has some unique operations left out, so the template method defines only a generic way to make any sandwich. When there are concrete sandwich subclasses overriding some of the sandwich's behaviors, clients can use only the same make message to make real sandwiches. The static relationship between AnySandwich and ConcreteSandwich is illustrated in a class diagram in Figure 18-2.

A class diagram of AnySandwich class with a ConcreteSandwich that overrides abstract operations that are called by a template method, make

Figure 18-2. A class diagram of AnySandwich class with a ConcreteSandwich that overrides abstract operations that are called by a template method, make

If we make an Objective-C class for AnySandwich, here we go in Listing 18-1.

Example 18-1. AnySandwich.h

@interface AnySandwich : NSObject
{
}

- (void) make;

// Steps to make a sandwich
- (void) prepareBread;
- (void) putBreadOnPlate;
- (void) addMeat;
- (void) addCondiments;
- (void) serve;

@end

Just like what we have seen in the class diagram in Figure 18-2, make will call the generic steps when it is invoked to make a sandwich, as shown in Listing 18-2.

Example 18-2. AnySandwich.m

#import "AnySandwich.h"

@implementation AnySandwich

- (void) make
{
  [self prepareBread];
  [self putBreadOnPlate];
[self addMeat];
  [self addCondiments];
  [self serve];
}

- (void) putBreadOnPlate
{
  // We need first to put bread on a plate for any sandwich.
}

- (void) serve
{
  // Any sandwich will be served eventually.
}

#pragma mark -
#pragma Details will be handled by subclasses

- (void) prepareBread
{
  // we need to make sure subclasses override this
}

- (void) addMeat
{
  // we need to make sure subclasses override this
}

- (void) addCondiments
{
  // we need to make sure subclasses override this
}

@end

The prepareBread, addMeat, and addCondiments methods are stubbed out because they need to be overridden by subclasses later to provide any specifics to real sandwiches. We need to provide some mechanism to make sure subclasses override those methods that make sense to a real sandwich-making process. We will discuss related issues in "Ensuring That the Template Method Is Working" section later in this chapter.

Now we want to make a real sandwich, a Reuben sandwich. Since a Reuben sandwich is a type of sandwich, steps to make it should be the same as any other sandwich, though it has its own ingredients and some little operations that are specific to it. An Objective-C class for a Reuben sandwich should look like Listing 18-3.

Example 18-3. ReubenSandwich.h

#import "AnySandwich.h"

@interface ReubenSandwich : AnySandwich
{
}

- (void) prepareBread;
- (void) addMeat;
- (void) addCondiments;
// ReubenSandwich's specific operations
- (void) cutRyeBread;
- (void) addCornBeef;
- (void) addSauerkraut;
- (void) addThousandIslandDressing;
- (void) addSwissCheese;

@end

ReubenSandwich is a subclass of AnySandwich. There are specific steps and ingredients to make a Reuben sandwich. A Reuben sandwich needs rye bread as bread, corn beef as meat, sauerkraut, Thousand Island dressing, and Swiss cheese as condiments. Even though cheese is not a type of condiment, we put it in there to simplify the generic steps to make any sandwich because not all sandwiches have cheese. We put the Reuben sandwich operations in the generic sandwich methods, as shown in Listing 18-4.

Example 18-4. ReubenSandwich.m

#import "ReubenSandwich.h"

@implementation ReubenSandwich

- (void) prepareBread
{
  [self cutRyeBread];
}

- (void) addMeat
{
  [self addCornBeef];
}

- (void) addCondiments
{
  [self addSauerkraut];
  [self addThousandIslandDressing];
  [self addSwissCheese];
}

#pragma mark -
#pragma mark ReubenSandwich Specific Methods

- (void) cutRyeBread
{
  // A Reuben sandwich requires two slices of rye bread
}

- (void) addCornBeef
{
  // ... add tons of corn beef
}
- (void) addSauerkraut
{
  // ... and sauerkraut.
}

- (void) addThousandIslandDressing
{
  // ... don't forget to put Thousand Island dressing
}

- (void) addSwissCheese
{
  // ... as well as some good Swiss cheese.
}

@end

Our ReubenSandwich doesn't need to care about putBreadOnPlate and serve. Our common AnySandwich has defined those behaviors, and other concrete sandwiches share them. What ReubenSandwich needs to care about are the bread, the meat, and any condiments.

The prepareBread method calls ReubenSandwich's specific method cutRyeBread to prepare slices of rye bread for the sandwich. If the sandwich class is ReubenSandwich, then the prepareBread method is actually preparing slices of rye bread, not anything else.

Almost any Reuben sandwich has corn beef in it. So our Reuben class has an addCornBeef method, which is, in fact, our actual addMeat step in our original AnySandwich abstract class. Of course, if you have your own version of a Reuben sandwich, then you can put a different type of meat in the sandwich or add your other meat with corn beef together.

OK, so the last step that a Reuben sandwich needs to care about is to addCondiments. We know there are a lot of variations, but let's stick with something common. For the condiments, we will addSauerkraut, addThousandIslandDressing, and addSwissCheese. So when addCondiments is invoked, ReubenSandwich will add sauerkraut, Thousand Island dressing, and Swiss cheese on the meat.

When the last step, serve, is done, then our Reuben sandwich will be ready to be enjoyed. What about other types of sandwiches? Can we use the same procedure to make ourselves a different sandwich? The answer is, yes, absolutely! How about a hamburger? Let's see how it looks in Listing 18-5.

Example 18-5. Hamburger.h

#import "AnySandwich.h"

@interface Hamburger : AnySandwich
{
}

- (void) prepareBread;
- (void) addMeat;
- (void) addCondiments;

// Hamburger specific methods
- (void) getBurgerBun;
- (void) addKetchup;
- (void) addMustard;
- (void) addBeefPatty;
- (void) addCheese;
- (void) addPickles;

@end

Hamburger is also a subclass of AnySandwich, it also has its own specifics to prepare. A hamburger needs a bun as bread, a beef patty (unless you want a double cheeseburger) as meat, ketchup, mustard, pickles, and cheese as condiments. Like ReubenSandwich, we are treating cheese as a type of condiment here for the same reason. When we put the specific hamburger operations in the slots of generic sandwich-making operations, the code should look like Listing 18-6.

Example 18-6. Hamburger.m

#import "Hamburger.h"

@implementation Hamburger

- (void) prepareBread;
{
  [self getBurgerBun];
}

- (void) addMeat
{
  [self addBeefPatty];
}

- (void) addCondiments
{
  [self addKetchup];
  [self addMustard];
  [self addCheese];
  [self addPickles];
}

#pragma mark -
#pragma mark Hamburger Specific Methods

- (void) getBurgerBun
{
  // A hamburger needs a bun.
}

- (void) addKetchup
{
  // Before adding anything to a bun, we need to put ketchup.
}

- (void) addMustard
{
  // Then add some mustard.
}

- (void) addBeefPatty
{
  // A piece of beef patty is the main character in a burger.
}

- (void) addCheese
{
  // Let's just assume every burger has cheese.
}

- (void) addPickles
{
  // Then finally add some pickles to it.
}

@end

There are so many combinations you can use to make your own sandwiches. All you need to do is to create your own sandwich class that provides specific implementation of prepareBread, addMeat, and addCondiments. Then by invoking the make method of an instance of AnySandwich (the parent class), the standard procedure will be carried out until the sandwich is ready to be served.

The "standard procedure" in our AnySandwich parent class represents our algorithm to make a sandwich. The algorithm is implemented in the make method with some stubbed-out operations; subclasses implement the details. So a subclass actually doesn't need to know any of the details of an algorithm, and at the same time the parent class doesn't need to know any details of any concrete operations that a subclass provides.

Ensuring That the Template Method Is Working

Objective-C doesn't provide any attributes to methods, so subclasses must override them. So how can we make sure the stubbed-out, generic sandwich-making methods are overridden by ReubenSandwich and Hamburger?

As we know, if a sandwich doesn't prepareBread, addMeat, or addCondiments, it's not a sandwich anymore (based on our original assumption about a sandwich). It means our application wouldn't make sense if any one of those steps were missing. That sounds like an exception in some sense (e.g., Oops! There is no meat in the addMeat method). There are many ways to do that. A simple one is to have the methods return a BOOL value with a default value set to NO. So the make method will check each operation's returned value to make sure no one is left open. But there is a catch—if for some reason the methods were reused and the new invoking method didn't have the same check to make sure the flow of the algorithm is correct, then the algorithm would be screwed. Why don't we throw an exception if the required primitive methods are not overridden? Throwing an instance of NSException guarantees the trouble will get caught as early as during development. The exception doesn't mean to be caught when the user is using your app though. When an exception is thrown, it can print an error message that includes the method and class names as well as the reason of the problem. Such information is invaluable to debug later if you did forget to override the abstract methods in a concrete AnySandwich subclass. After we put some exception throwing statements in those methods, they would look something like Listing 18-7.

Example 18-7. A Statement That Raises an Instance of NSException Is Added in Each of the Original Stubbed-Out, Generic Sandwich-Making Methods

- (void) prepareBread
{
  [NSException raise:NSInternalInconsistencyException
              format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];
}

- (void) addMeat
{
  [NSException raise:NSInternalInconsistencyException
              format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];
}

- (void) addCondiments
{
  [NSException raise:NSInternalInconsistencyException
              format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];
}

The prepareBread, addMeat, and addCondiments methods were no-op in the original version, and now each of them gets fleshed out with raising an instance of NSException. When a client invokes the make method in AnySandwich and it hits the AnySandwich version of prepareBread, then an exception will be thrown. raise is a class method of NSException that takes a name and a message of the exception. We are using NSInternalInconsistencyException defined in the Cocoa Touch framework to imply an unexpected condition within the called code. You can define your own exception name there. It is totally up to you. The message part of the method accepts a string format to construct an NSString. We use _cmd as part of the message. _cmd is an Objective-C object attribute that contains the name of a selector being invoked or forwarded. In Objective-C 2.0, you can use the following:

@throw [NSException exceptionWithName:... reason:... userInfo:...];

The @throw keyword with NSException does pretty much the same thing. However, it is simpler to use the raise class method of NSException.

If there is no @catch block in the call stack for an exception thrown from the prepareBread method, you will see a message printed in a crash log like the following (for the sake of brevity, a complete call stack printout is omitted):

[Session started at 2010-08-01 18:16:59 −0700.]
2010-08-01 18:17:00.632 TemplateMethod[1315:207] *** Terminating app due to uncaught
exception 'NSInternalInconsistencyException', reason: 'You must override prepareBread in
a subclass'

Adding an Extra Step to the Template Method

OK, now we know how to implement the Template Method pattern in our previous sandwich-making process. It would be nice if we could add a slight touch to making some sandwiches—for example, grilling our ReubenSandwich a little bit before it's served. But that final touch is optional in general, which means not all sandwiches have to have an extra step. Also the default implementation of the extra step is a no-op. A method in a parent class that a subclass can extend if necessary is called a "hook," and a hook method usually does nothing by default.

Let's go back to our AnySandwich class. We can provide an optional hook method called extraStep for any subclassed sandwich to add some final touches if it is necessary before it's served. A newer version of AnySandwich class is like Listing 18-8.

Example 18-8. AnySandwich.h with an Extra Step

@interface AnySandwich : NSObject
{
}

- (void) make;

// Steps to make a sandwich
- (void) prepareBread;
- (void) putBreadOnPlate;
- (void) addMeat;
- (void) addCondiments;
- (void) extraStep;
- (void) serve;

@end

In the original make method, extraStep is the second-to-last operation right before serve, as shown in Listing 18-9.

Example 18-9. The make Method of AnySandwich.m with extraStep

- (void) make
{
  [self prepareBread];
  [self putBreadOnPlate];
  [self addMeat];
  [self addCondiments];
[self extraStep];
  [self serve];
}

So for any sandwich that needs an extra step or two before it's served, it can override the extraStep method. The method will be called exactly one step before the serve method in the make method. Since the extraStep is optional, it won't hurt the whole sandwich-making process in general if the method is empty. Also, it doesn't need to raise an exception to guarantee it's overridden by any subclass.

We know our ReubenSandwich would taste a lot better if it's grilled a little bit, so we will add some implementation to the Reubensandwich class to support that (Listing 18-10).

Example 18-10. ReubenSandwich Overrides extraStep and Adds grillIt in the Header File

#import "AnySandwich.h"

@interface ReubenSandwich : AnySandwich
{
}

- (void) prepareBread;
- (void) addMeat;
- (void) addCondiments;
- (void) extraStep;

// ReubenSandwich specific methods
- (void) cutRyeBread;
- (void) addCornBeef;
- (void) addSauerkraut;
- (void) addThousandIslandDressing;
- (void) addSwissCheese;
- (void) grillIt;

@end

Its implementation with grillIt as an extraStep is shown in Listing 18-11.

Example 18-11. ReubenSandwich Has grillIt As Its extraStep

#import "ReubenSandwich.h"

@implementation ReubenSandwich

- (void) prepareBread
{
  [self cutRyeBread];
}

- (void) addMeat
{
  [self addCornBeef];
}

- (void) addCondiments
{
  [self addSauerkraut];
  [self addThousandIslandDressing];
[self addSwissCheese];
}

- (void) extraStep
{
  [self grillIt];
}

#pragma mark -
#pragma mark ReubenSandwich Specific Methods

- (void) cutRyeBread
{
  // A Reuben sandwich requires two slices of rye bread
}

- (void) addCornBeef
{
  // ... add tons of corn beef
}

- (void) addSauerkraut
{
  // ... and sauerkraut.
}

- (void) addThousandIslandDressing
{
  // ... don't forget to put Thousand Island dressing
}

- (void) addSwissCheese
{
  // ... as well as some good Swiss cheese.
}

- (void) grillIt
{
  // finally it needs to be toasted.
}

@end

If you have more extra steps for your own version of a Reuben sandwich, feel free to add them to the extraStep method. When the method is invoked, whatever is in it will be invoked as well. So there is no need for modifying AnySandwich for more optional extra steps. From now on, when we make our Reuben sandwich again, it will be grilled. However, our Hamburger doesn't need to do any extra steps like our ReubenSandwich does. The modified algorithm in our AnySandwich class won't affect other sandwiches at all, as the extraStep is optional.

Note

A hook method often does nothing by default and is optional for subclasses.

Using the Template Method in the Cocoa Touch Framework

The Template Method pattern is quite common in framework designs. Template methods are a fundamental technique for code reuse. They allow any designer of a framework to leave out some application-specific elements of an algorithm to an application. They are the means for factoring out common behavior in framework classes. This approach can help extensibility and reusability (with the same "recipe") yet maintain loose coupling between different classes (the framework classes vs. yours). The Cocoa Touch framework has also adapted the Template Method patterns. You can find those framework classes here and there within the framework, though they may not be as common as Delegation. In this section, we are going to explore a couple of common template methods in the framework.

Custom Drawing in the UIView Class

Since iOS 2.0, there is a method in the UIView class that allows an application to perform custom drawing by overriding its method:

- (void)drawRect:(CGRect)rect

The default implementation of the method does nothing. Subclasses of the UIView override this method if they actually need to draw their own views. So the method is a hook method.

When there is a need to change the view on the screen, this method will be invoked. The framework handles all the low-level, dirty work to make it happen. Part of the drawing process handled by UIView is to invoke drawRect:. If there is any code in that method, it would be invoked as well. Subclasses can use Quartz 2D function UIGraphicsGetCurrentContext to get the current graphics context for any available 2D elements in the framework.

The client application can also activate the drawing process manually by invoking a UIView's method.

- (void)setNeedsDisplay

It notifies an instance of UIView so that it redraws its entire rectangle on the screen. You can also specify a particular region on the view as a rectangle to be redrawn by using another instance method:

- (void)setNeedsDisplayInRect:(CGRect)invalidRect

invalidRect is the rectangular region of the receiver to mark it as invalid; it means that's the only region needs to be redrawn but nowhere else.

Let's see another framework class that also implements the Template Method pattern.

Other Template Method Implementations in Cocoa Touch

We cannot cover every Template Method adaptation in the framework, but the last one that is worth mentioning is UIViewController. It defines some methods that let user applications handle the different orientations of the device. The following is a list of messages that appear when different orientation is changed.

shouldAutorotateToInterfaceOrientation:
rotatingHeaderView
rotatingFooterView
willAnimateRotationToInterfaceOrientation:duration:
willRotateToInterfaceOrientation:duration:
willAnimateFirstHalfOfRotationToInterfaceOrientation:duration:
didAnimateFirstHalfOfRotationToInterfaceOrientation:
willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:
didRotateFromInterfaceOrientation:

They are pure hook methods that are waiting to be overridden for some real action. Each one of them will be invoked during specific moments of the device's orientation changes. Subclasses can override selected methods to augment specific behavior during the screen rotation process at different steps and times.

Summary

The Template Method pattern is a fundamental technique for code reuse. Template methods are important in framework class designs, as they are the means for factoring out common behavior in framework classes.

Template methods can be found in many places in the Cocoa Touch framework. We have discussed only a few of them in the previous sections. You can find more in the iOS developer's documentation.

In the next chapter, we are going to discuss another way to encapsulate algorithms in objects as different strategies.



[16] 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
18.221.123.73