SWDev Story

SWDev Story: Structural Patterns

monolithic, data flow, factored

3.0 What are Structural Patterns?

Structural patterns are recurring ways of organizing code into packages and components. They are not frameworks or libraries - they are vocabulary for describing how responsibilities are divided and how components communicate. This chapter and the next cover five patterns that appear in the Design Bites sequence, each illustrated with a line-counting program similar in scope to TextFinder:
  • Monolithic (Basic) - one package, one struct
  • Data Flow - components connected by typed event channels
  • Factored - policy separated from mechanism
  • Type Erasure - concrete types hidden behind trait objects (next chapter)
  • Plugin - behavior loaded at runtime (next chapter)
No pattern is universally best. Each has tradeoffs between simplicity, flexibility, testability, and runtime cost. Knowing all five gives you the vocabulary to choose deliberately rather than by default.

3.1 Monolithic (Basic) Structure

In a monolithic structure all code lives in a single package. A single struct or class holds the state and provides all the methods needed to read input, do the work, and produce output.
Package: LineCounter
  struct LineCounter {
    root: path,
    extensions: list<string>
  }
  impl LineCounter {
    fn new(root, exts) -> Self
    fn run() -> counts      // walks dirs, opens files, counts lines
    fn report(counts)       // formats output
  }
Monolithic - Pros and Cons
Notes
Pros Simple to create and navigate; no inter-package plumbing; one piece to track and deploy.
Cons All concerns are mixed together; difficult to unit-test individual parts; adding a new output format or traversal strategy requires editing the same struct; grows harder to understand as the codebase expands.
Best for Small, short-lived scripts or proofs of concept where simplicity is the only priority.

3.2 Data Flow Structure

A data flow structure decomposes the program into components connected by typed channels or event callbacks. Data flows from one component to the next; each component has no knowledge of its neighbors' implementations.
Packages: DirNav, TextProcessor, Collector

  DirNav discovers files and fires a FileEvent for each.
  TextProcessor receives FileEvents, opens files, emits LineEvents.
  Collector receives LineEvents and accumulates counts.

  Data flow: DirNav --FileEvent--> TextProcessor --LineEvent--> Collector
The event types are the contracts between stages. Each stage only depends on the event type, not on the stage that produces it. You can swap DirNav for a network source without changing TextProcessor.
Data Flow - Pros and Cons
Notes
Pros Components are decoupled at the event boundary; easy to insert new stages or replace existing ones; natural fit for concurrent pipelines.
Cons More indirection than monolithic; event types must be designed carefully to carry enough context; debugging a broken pipeline requires tracing events across stages.
Best for Processing pipelines, streaming data, and systems where stages need to evolve independently or run concurrently.

3.3 Factored Structure

A factored structure separates policy from mechanism. The policy decides what to do and in what order. The mechanisms implement the individual operations. The policy component depends on abstract interfaces, not on concrete implementations.
Packages: Executive (policy), DirNav, FileReader, Counter (mechanisms)

  Executive holds references to abstract traits:
    trait Traverser   { fn walk(root) -> iter<path>; }
    trait Reader      { fn read(path) -> iter<line>; }
    trait Accumulator { fn add(line); fn total() -> usize; }

  Executive::run() {
    for path in self.traverser.walk(root) {
      for line in self.reader.read(path) {
        self.accumulator.add(line);
      }
    }
  }
Concrete types (DirNav, FileReader, Counter) implement the traits. Executive never imports them directly. To substitute a mock reader for testing, inject a different implementation of Reader.
Factored - Pros and Cons
Notes
Pros Policy and mechanisms are independently testable and replaceable; adding a new traversal strategy (e.g., zip file search) requires only a new implementation of Traverser, not changes to Executive.
Cons More upfront design effort; introduces trait objects or abstract base classes; slight indirection cost at each interface call.
Best for Systems where the core algorithm is stable but the implementations of its steps are expected to vary or grow over time.

3.4 References

Structural Patterns References
ResourceDescription
Design Bites: Monolithic Detailed walkthrough of the basic monolithic structure with code examples.
Design Bites: Factored Factored structure with trait-based policy and mechanism separation.
Design Bites: Data Flow Data flow structure with event types and pipeline composition.
Design Bites: Type Erase Type erasure structure hiding concrete types behind trait objects.
Design Bites: Plugin Plugin structure with behavior loaded at runtime via dynamic dispatch.