Skip to main content

Composition, Functional Cores, and Extension Axes

What This Concept Is​

OOD does not require representing every operation by a class. Choose between a value, function, module or object based on the state and decisions it needs to hide. A pure calculation can accept immutable values and return a result. An outer coordinator handles I/O, time and resource ownership. This often makes policy easier to test while leaving stateful invariants together where they belong.

Why It Matters Here​

The lab needs interchangeable policies and a read-only account consumer. Comparing functions with objects helps distinguish a useful boundary from syntax added only to satisfy a pattern name.

Concrete Example​

A shipping quote requires positive integer grams and a supplied rate function. One policy charges a flat amount; another charges by rounded-up weight bands. quote(weight, rate) validates the boundary and invokes the callable once. No inheritance or DI container is needed. If the policy later needs lifecycle, cached state or several cohesive operations, a callable object or explicit interface may be more appropriate. Do not move infrastructure failures into a misleading zero fee.

For a closed group of report types, a central exhaustive match can expose missing cases. For externally supplied report implementations with a stable operation, polymorphism can keep the consumer stable. Compare adding a variant and adding an operation: each representation pays for a different direction of change.

Common Confusion / Misconception​

Pure functions still need data contracts, validation and resource bounds. A functional core does not eliminate complexity or make repeated calculations free. An injected fake can drift from the real provider. Share behavioral checks where possible and keep integration evidence for the actual adapter; a mock expectation cannot establish database or network semantics.

How To Use It​

Identify state, effects and lifecycle first. Define the smallest cohesive consumer contract, supply the collaborator at assembly, and test its failures. Compare adding a new variant with adding an operation over every existing variant.

Check Yourself​

What does an injected fake establish about the real adapter? When would a rate policy need owned state? Which edit surface changes when a new operation is added?

Mini Drill or Application​

Practice: provide two rate callables with no common base class, then describe one reason to replace them with a cohesive object. Feedback: a genuine shared state/lifecycle requirement can justify the object; “every function needs an interface” cannot.

Read This Only If Stuck​

Read Good Code, Bad Code's book index selectively for layers, interfaces and dependency injection.

Use the design contracts and labs to turn these claims into evidence.