Cooking Up a Good Template Method

The software concept of "raising the level of abstraction" has improved my skill and creativity in cooking, by teaching me to think about recipe components in terms of their properties and functions. Practicing abstraction-raising in cooking feeds back to help me with coding; for example, keeping me from going astray the other day with the Template Method pattern. This post is more about coding than cooking. The cooking's a metaphor. (The cake is a lie.)

Abstract Cooking
My skill with cooking grew from rote recipe following to intuitive creation when I started to think of it in terms borrowed from software: raising the level of abstraction.

Consider a week-night skillet dinner. If I told you to heat canola oil in a cast-iron skillet, saute slices of onion and chunks of chicken seasoned with salt and pepper, and toss in bell peppers cut into strips, you could probably follow along and make exactly that. But that's pretty limiting. If instead I described the process as using a fat to conduct heat for sauteing a savory root, a seasoned protein, and some vegetables, then you could use that as a template, and make a week of dinners without repeating yourself.

Let's dive into that step of using a fat for conduction, because it is a cool and useful bit of food science. To cook, you need to get heat onto food. The medium can be air, liquid, or fat. Each creates different results, hence the terms baking, boiling, and frying. When you toss cut-up bits of food in a skillet with oil and repeatedly jostle them, you're sauteing ("saute" means "to jump"), and that oil is playing the role of the fat, which is conducting the heat. If you'll pardon the metaphor, CanolaOil implements the IFat interface.

It's useful to think of cooking this way, because if you know the properties of the various cooking fats, you can choose the right IFat implementation for the job. Canola oil is heart-healthy and stands up well to stove-top heat. Olive oil has wonderful health benefits, a bold flavor, and an intriguing green color, but those attributes are pretty much obliterated by heat, so save your expensive EVOO for raw applications like salads and dips. Butter makes everything taste better, browns up beautifully, but is harder on the heart and will burn at a low temperature; temper it with an oil like canola to keep it from burning. Peanut oil stands up to heat like a champ, so it's popular for deep frying. Armed with this kind of knowledge, I don't need to check a recipe when I'm cooking; I just think about what I'm trying to accomplish, and choose the right implementation.

Pam Anderson's How to Cook Without a Book got me thinking about food this way, and Harold McGee's On Food and Cooking provides a feast of food geekery to fill in all the particulars.

Template Coding
Thinking about food this way, raising the level of abstraction, guides my thinking about code. My meal preparation follows the Template Method pattern, as does a class my teammate and I needed to modify recently.

In this example, our application sends instructions to various external systems. The specifics of how those systems like to hold their conversations vary between systems. However, the series of steps, when phrased in our core business terms, remain the same. You do A, then you do B, then you do C, in whatever way a particular instance likes to do A, B, and C.

Here's my class with its template method, translated back to the dinner metaphor:

    3     public abstract class SkilletDinner

    4     {

    5         public void Cook()

    6         {

    7             HeatFat();

    8             SauteSavoryRoot();

    9             SauteProtein();

   10             SauteVegetables();

   11         }

   12 

   13         protected abstract void HeatFat();

   14         protected abstract void SauteSavoryRoot();

   15         protected abstract void SauteProtein();

   16         protected abstract void SauteVegetables();

   17     }


But lo, I encountered an external system that needed to do one extra little thing. I needed a special step, just for that one instance. Like dinner the other night, where the vegetable was asparagus, the fat was bacon (oh ho!), and the final step was to toss some panko breadcrumbs into the pan to brown and toast and soak up the bacony love.

How do I extend my template method to accommodate an instance-specific step?

One idea that floated by was to make the method virtual, so that we could override it in our special instance. But we still wanted the rest of the steps, so we'd have to copy the whole method into the new instance, just to add a few lines. Also, anybody else could override that template, too, so that when they were told to do A, B, and C, they could totally fib and do nothing of the sort.

    3     public abstract class SkilletDinner

    4     {

    5         public virtual void Cook()

    6         {

    7             //Note: The Cook template method is now virtual,

    8             //and can be overridden in deriving classes.

    9             //That's not good.

   10             HeatFat();

   11             SauteSavoryRoot();

   12             SauteProtein();

   13             SauteVegetables();

   14         }

   15         protected abstract void HeatFat();

   16         protected abstract void SauteSavoryRoot();

   17         protected abstract void SauteProtein();

   18         protected abstract void SauteVegetables();

   19     }

   20 

   21     public class LazyDinner : SkilletDinner

   22     {

   23         public override void Cook()

   24         {

   25             OrderPizza();

   26             //We're overriding the template and *cheating*!

   27             //Although, if it's Austin's Pizza,

   28             //maybe that's okay...

   29         }

   30 

   31         private void OrderPizza()

   32         {

   33             //With extra garlic!

   34         }

   35 

   36         protected override void HeatFat() { }

   37         protected override void SauteSavoryRoot() { }

   38         protected override void SauteProtein() { }

   39         protected override void SauteVegetables() { }

   40     }


That LazyDinner class isn't really a SkilletDinner at all; its behavior is completely different. No, that option flouts the whole point of the Template Method pattern.

Our better idea was to make one small change to the template method, adding an extension point. That is, a call to a virtual method which in the base implementation does nothing, and can be overridden and told to do stuff in specific cases.

Back to dinner:

    3     public abstract class SkilletDinner

    4     {

    5         public void Cook()

    6         {

    7             HeatFat();

    8             SauteSavoryRoot();

    9             SauteProtein();

   10             SauteVegetables();

   11             AddFinishingTouches(); //Here's the hook.

   12         }

   13 

   14         protected virtual void AddFinishingTouches()

   15         {

   16             //By default, do nothing.

   17         }

   18 

   19         protected abstract void HeatFat();

   20         protected abstract void SauteSavoryRoot();

   21         protected abstract void SauteProtein();

   22         protected abstract void SauteVegetables();

   23     }

   24 

   25     public class FancyBaconPankoDinner : SkilletDinner

   26     {

   27         protected override void AddFinishingTouches()

   28         {

   29             //In this case, override this extensibility hook:

   30             ToastBreadcrumbs();

   31         }

   32 

   33         private void ToastBreadcrumbs()

   34         {

   35             //Toss in the bacon fat; keep 'em moving.

   36         }

   37 

   38         protected override void HeatFat()

   39         {

   40             //Cook bacon, set aside, drain off some fat.

   41         }

   42 

   43         protected override void SauteSavoryRoot()

   44         {

   45             //Minced garlic, until soft but before browning

   46         }

   47 

   48         protected override void SauteProtein()

   49         {

   50             //How about... tofu that tastes like bacon?

   51         }

   52 

   53         protected override void SauteVegetables()

   54         {

   55             //Asparagus, cut into sections.

   56             //Make it bright green and a little crispy.

   57         }

   58     }


This maintains the contract of the template method, while allowing for special cases. With the right extensibility hooks in place, my dinner preparation happily follows the Open-Closed Principle—open for extension, but closed for modification.

I enjoy the way my various hobbies feed into and reflect upon each other. I hope this post has given you some useful insight into the Template Method pattern, or dinner preparation, or both. Look for synergies amongst your own varied interests; it can be the springboard for some truly breakthrough ideas.

Mmm, bacon...

2 comments:

Martha said...

This is really clear, even to an old programmer like me. Plus, can I come to your house for dinner real soon?

Sharon said...

^_^ Yes, moms and dads have a standing invitation.