Delegation and Indirection

ScopingClosuresEncapsulation

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:

sql
    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:

python
    // 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 else

Why 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:

python
    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)
python
    // 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:

python
    // 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:

wollok
    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:

  • validator knows about rules
  • board knows about positions
  • history knows 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:

wollok
    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.

python
    game.scoring_strategy = AggressiveScoring()
    // or: game.scoring_strategy = DefensiveScoring()

Decorator: Delegate then add something.

python
    logged_board = LoggingDecorator(board)
    // All calls go to board, but get logged

Proxy: Delegate everything to a stand-in.

python
    remote_game = GameProxy(network_connection)
    // Looks like a game, delegates over network

When 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 Piece

This 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.