Delegation and Indirection
A general doesn't fight every battle personally. She delegates. "You take the left flank. You defend the bridge. Report back when it's done." The general coordinates; others execute.
Delegation is everywhere in programming: one piece of code asks another to do something. It's how decomposed parts collaborate. It's the glue that holds systems together.
The Simplest Delegation
At its simplest, delegation is calling a function:
validate_move(board, move):
// I don't know how to check positions
// I delegate to someone who does
if not is_valid_position(move.from):
return false
if not is_valid_position(move.to):
return false
... validate_move doesn't implement position checking. It delegates to is_valid_position. This is separation of concerns: each function handles its own concern.
Indirection: The Extra Layer
Sometimes delegation goes through an intermediate---an indirection:
// Direct: call the function you want
result = calculate_score(position)
// Indirect: go through something that decides
result = evaluator.evaluate(position)
// evaluator might use calculate_score, or something elseWhy add layers? Flexibility. The indirect version can:
- Choose different implementations
- Change behavior without changing callers
- Add logging, caching, or other concerns
Procedural: Callbacks
In procedural code, delegation often happens through callbacks---functions passed as parameters:
sort(items, compare_function):
// I know HOW to sort
// but not how to COMPARE
// Caller tells me via callback
for i in items:
for j in items:
if compare_function(i, j) > 0:
swap(i, j) // Delegate comparison policy
sort(moves, (a, b) => a.quality - b.quality)
sort(pieces, (a, b) => a.value - b.value)Functional: Higher-Order Functions
Functional programming makes delegation a first-class pattern through higher-order functions:
// map delegates transformation
map(pieces, (p) => p.value)
// filter delegates selection
filter(moves, (m) => is_capture(m))
// reduce delegates combination
reduce(values, 0, (sum, v) => sum + v)The pattern separates:
The function handles structure; the caller provides behavior.
- Structure: How to traverse, accumulate, combine
- Behavior: What to do at each step
Object-Oriented: Message Passing
In object-oriented programming, delegation happens through messages between objects:
class Game {
var validator
var board
var history
method makeMove(move) {
// Delegate to validator
if (!validator.isValid(move)) return error
// Delegate to board
board.apply(move)
// Delegate to history
history.record(move)
}
}The Game object holds references to collaborators:
validatorknows about rulesboardknows about positionshistoryknows about the past
Each handles its own responsibility. The Game just asks them.
The Power of Polymorphic Delegation
Object-oriented programming's superpower is polymorphic \concept{delegation}---delegating without knowing the exact recipient:
class Game {
var white
var currentPlayer
var board
method setWhitePlayer(player) {
white = player // could be Human, AI, NetworkPlayer...
}
method playTurn() {
const move = currentPlayer.chooseMove(board)
// Don't know HOW they choose. Don't care!
board.apply(move)
}
}This enables:
- Substitution: Swap implementations without changing code
- Extension: Add new player types later
- Testing: Inject mock players for testing
Patterns of Delegation
Several classic patterns are fundamentally about delegation:
Strategy: Delegate an algorithm.
game.scoring_strategy = AggressiveScoring()
// or: game.scoring_strategy = DefensiveScoring()Decorator: Delegate then add something.
logged_board = LoggingDecorator(board)
// All calls go to board, but get loggedProxy: Delegate everything to a stand-in.
remote_game = GameProxy(network_connection)
// Looks like a game, delegates over networkWhen to Delegate
Delegate when:
Don't over-delegate:
- Someone else already knows how
- The behavior might vary or change
- You want to separate concerns
- You want to enable testing or extension
- Every delegation adds indirection cost
- Too many layers obscure what's happening
- Simple, unlikely-to-change logic can stay inline
The Delegation Chain
In real systems, delegation chains through many layers:
User clicks "Move"
→ UI delegates to Controller
→ Controller delegates to Game
→ Game delegates to Board
→ Board delegates to Square
→ Square updates PieceThis is both the power and danger of delegation:
- Power: Separation of concerns, flexibility
- Danger: Hard to follow, distributed complexity
Good design balances depth of delegation with clarity of flow.
We've now explored the fundamental building blocks: iteration (processing many), decomposition (breaking apart), scoping (controlling visibility), and delegation (asking for help). These patterns appear in every paradigm, wearing different clothes but serving the same purposes.
In the chapters ahead, we'll see these building blocks applied---first in functional programming, then in object-oriented design. The vocabulary you've learned here will help you recognize the same ideas across different styles.