Template Method Pattern Using Protocols

Learn how to model Template Method in Clojure with a shared orchestration function and protocol-defined variation points, and when plain data-plus-functions is simpler than type-based templates.

Template Method pattern: A pattern where the overall algorithm stays fixed, but specific steps are supplied by specialized implementations.

In Clojure, Template Method usually works best as a plain orchestration function plus explicit variation points. Protocols can express those variation points when type-based dispatch is real, but they are not mandatory. Often the important idea is the stable workflow, not the mechanism used to customize it.

Start with the Shared Workflow

The core of Template Method is a function that fixes the sequence of steps:

1(defn run-import [impl payload]
2  (let [parsed   (parse-payload impl payload)
3        checked  (validate-payload impl parsed)
4        prepared (prepare-records impl checked)]
5    (persist-records impl prepared)))

The orchestration is stable. The variation lives in the step implementations.

Protocols Work When Type-Based Variation Is Real

1(defprotocol Importer
2  (parse-payload [this payload])
3  (validate-payload [this parsed])
4  (prepare-records [this checked])
5  (persist-records [this prepared]))

This works well when different importer types genuinely have different implementations. The protocol makes those extension points explicit without forcing the caller to understand every detail.

A Better Question: Do You Need Type Dispatch?

Protocols are helpful when:

  • the workflow is stable
  • there are multiple concrete implementations
  • dispatch should depend on the type of implementation

They are less helpful when:

  • there are only one or two variants
  • configuration data, not type, chooses the variation
  • a simple map of functions would be clearer

In Clojure, a lot of Template Method examples can be simplified into data plus higher-order functions. That is often a feature, not a loss.

A Map-of-Functions Version Can Be Cleaner

 1(def csv-importer
 2  {:parse   parse-csv
 3   :validate validate-csv
 4   :prepare prepare-csv-records
 5   :persist persist-csv-records})
 6
 7(defn run-import* [impl payload]
 8  (let [parsed   ((:parse impl) payload)
 9        checked  ((:validate impl) parsed)
10        prepared ((:prepare impl) checked)]
11    ((:persist impl) prepared)))

If the variation is configuration-driven rather than type-driven, this shape is often easier to test and inspect.

The Pattern at a Glance

    flowchart LR
	    A["Shared workflow"] --> B["Step 1"]
	    B --> C["Step 2"]
	    C --> D["Step 3"]
	    D --> E["Result"]
	    F["Implementation-specific behavior"] --> B
	    F --> C
	    F --> D

The main point is that the overall sequence stays fixed while the implementation details of some steps can vary.

Common Failure Modes

Template Method becomes awkward when:

  • the shared algorithm is not actually stable
  • too many optional steps are bolted onto one template
  • protocols are used when a function map would be simpler
  • subclasses or implementations smuggle in hidden side effects

If every new variant needs a different workflow shape, you do not have one template. You have several workflows that should probably be modeled separately.

Practical Heuristics

Use Template Method when the high-level sequence is genuinely durable and the variation points are narrow. In Clojure, start with a shared function. Add protocols only when type-based polymorphism actually buys clarity.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026