Skip to main content

Template Method Pattern

Definition​

The Template Method defines the skeleton of an algorithm in a method, deferring some steps to subclasses. It lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

Coffee Shop Example​

Every hot beverage follows the same preparation algorithm: grind beans β†’ brew β†’ pour in cup β†’ add condiments. But each beverage type customizes specific steps β€” espresso uses fine grind and no condiments, while latte uses medium grind and adds steamed milk.

Structure​

Abstract Class β€” The Algorithm Skeleton​

public abstract class BeverageMaker
{
// Template Method β€” defines the algorithm skeleton
// Marked sealed so subclasses can't override the overall structure
public void Prepare()
{
GrindBeans();
Brew();
PourInCup();
if (CustomerWantsCondiments())
AddCondiments();
Console.WriteLine();
}

// Abstract steps β€” MUST be overridden
protected abstract void GrindBeans();
protected abstract void Brew();
protected abstract void AddCondiments();

// Concrete step β€” shared by all beverages
protected virtual void PourInCup() =>
Console.WriteLine(" Pouring into cup...");

// Hook β€” optional override (default behavior provided)
protected virtual bool CustomerWantsCondiments() => true;
}

Concrete Classes β€” Customizing Steps​

public class EspressoMaker : BeverageMaker
{
protected override void GrindBeans() =>
Console.WriteLine(" Grinding 18g fine espresso beans");

protected override void Brew() =>
Console.WriteLine(" Extracting at 9 bar pressure for 25 seconds");

protected override void AddCondiments() =>
Console.WriteLine(" Serving straight β€” no condiments for espresso");

// Hook override β€” espresso purists don't want extras
protected override bool CustomerWantsCondiments() => false;
}

public class LatteMaker : BeverageMaker
{
protected override void GrindBeans() =>
Console.WriteLine(" Grinding 18g medium espresso beans");

protected override void Brew() =>
Console.WriteLine(" Pulling double espresso shot");

protected override void AddCondiments() =>
Console.WriteLine(" Adding steamed milk (silky microfoam)");
}

public class CappuccinoMaker : BeverageMaker
{
protected override void GrindBeans() =>
Console.WriteLine(" Grinding 18g fine espresso beans");

protected override void Brew() =>
Console.WriteLine(" Pulling espresso shot");

protected override void AddCondiments() =>
Console.WriteLine(" Adding steamed milk + thick foam layer");
}

Client​

var makers = new BeverageMaker[]
{
new EspressoMaker(),
new LatteMaker(),
new CappuccinoMaker(),
};

foreach (var maker in makers)
{
Console.WriteLine($"Preparing {maker.GetType().Name.Replace("Maker", "")}:");
maker.Prepare();
}

// Output:
// Preparing Espresso:
// Grinding 18g fine espresso beans
// Extracting at 9 bar pressure for 25 seconds
// Pouring into cup...
//
// Preparing Latte:
// Grinding 18g medium espresso beans
// Pulling double espresso shot
// Pouring into cup...
// Adding steamed milk (silky microfoam)
//
// Preparing Cappuccino:
// Grinding 18g fine espresso beans
// Pulling espresso shot
// Pouring into cup...
// Adding steamed milk + thick foam layer
tip

Notice the EspressoMaker overrides the hook CustomerWantsCondiments() to return false. The algorithm skips AddCondiments() entirely. This is a hook β€” a method with a default behavior that subclasses can optionally override.

Types of Methods in Template Method​

TypePurposeModifier
Template MethodDefines the algorithm skeletonpublic sealed
Abstract methodsMust be overridden by subclassesprotected abstract
Concrete methodsShared behavior (default implementation)protected virtual
HooksOptional overrides with default behaviorprotected virtual returning bool

The Hollywood Principle​

"Don't call us, we'll call you."

The base class calls the subclass methods, not the other way around. The template method controls the flow β€” subclasses provide implementation details. This is the Hollywood Principle in action.

Template Method vs Strategy​

Template MethodStrategy
MechanismInheritanceComposition
When to chooseAlgorithm structure is fixed, steps varyEntire algorithm varies
ExtensibilitySubclass per variationNew strategy class
CouplingTighter (inheritance)Looser (composition)
Runtime changeNo (fixed at compile time)Yes (inject different strategy)

.NET Real-World Usage​

  • Stream β€” Read() / Write() are template methods; subclasses override ReadByte() / WriteByte()
  • ASP.NET Core ControllerBase β€” lifecycle methods like OnActionExecuting()
  • IHostedService β€” StartAsync / StopAsync as template for background services
  • BackgroundService β€” ExecuteAsync() is a template method with the skeleton provided
  • Test fixtures β€” SetUp() / TearDown() in unit testing frameworks

When to Use​

  • You have a fixed algorithm structure with variable steps
  • Common behavior across subclasses should be in one place
  • You want to control the extension points (Hollywood Principle)

When NOT to Use​

  • The algorithm varies entirely β€” use Strategy instead
  • There are few common steps β€” inheritance overhead isn't worth it
  • You need to change behavior at runtime β€” Strategy is better

Key Takeaways​

  • Template Method defines the algorithm skeleton in a base class
  • Subclasses override specific steps without changing the overall structure
  • Hooks provide optional customization points with default behavior
  • Uses inheritance β€” if composition is preferred, use Strategy

Interview Questions​

Q: Why mark the template method as sealed? To prevent subclasses from changing the algorithm's structure. The whole point is that the base class controls the flow. If subclasses can override the template method itself, the guarantee is lost.

Q: How is Template Method different from polymorphism? Template Method is a specific use of polymorphism. Regular polymorphism means a subclass overrides a method. Template Method adds a fixed calling order β€” the base class calls multiple abstract/virtual methods in a specific sequence. The structure is the key innovation.

Q: What's a "hook" in Template Method? A hook is a method with a default implementation that subclasses can optionally override. It's not abstract β€” it provides a sensible default. Example: CustomerWantsCondiments() returns true by default, but EspressoMaker overrides it to return false.