Design Patterns -- A Reader's Guide
Goal: recognize the recurring design patterns Flux actually uses (which is not the same as a textbook tour). For each pattern: what it looks like in code, where to find canonical examples, and what to watch out for when modifying. Reading time: 45 minutes.
1. How to read this chapter
Flux is ~25 years of evolved code. Some patterns are pristine; some are intentional pragmatic deviations. We list both.
For each pattern:
-
Form in Flux — how it looks in this codebase, not in a textbook.
-
Where — concrete file references.
-
Watch out for — pitfalls when extending.
2. Strategy — selectable algorithm behind an interface
Form in Flux
A small interface (IBendCalc, IFlexSpine, Glob subtype hierarchy) plus concrete implementations chosen at runtime by a property accessor or factory.
Canonical example: IBendCalc
Where: Src\Core\Bend\BendCalc.cs.
public interface IBendCalc {
void GetDieVRange (double thickness, out int min, out int max);
double SuggestDieWidth (double thickness, double radius, double interiorAngle);
double SuggestPunchRadius (double thickness);
double GetBendRadiusFactor (double thickness, double interiorAngle);
BendStaples GetBendStaples (Workpiece workpiece, ToolSet toolSet, EBendKind kind);
}
Implementations: StdBendCalc, TBSBendCalc (Trumpf).
Selection: in BendMachine.BendCalc:
public IBendCalc BendCalc => Settings.BendCalc switch {
Sheaf.EBendCalc.Default => IsTrumpf ? TBSBendCalc.It : StdBendCalc.It,
_ => StdBendCalc.It,
};
Watch out for
-
The selection key (here
Settings.BendCalc) lives inSheaf. Adding a new strategy requires extending the enum and the switch. -
Strategy instances are typically singletons (
StdBendCalc.It). Don’t add per-instance state.
Other examples
-
IFlexSpine(multiple implementations per bend kind). -
Crash engines (
BendOpCrashvs.NBendCrash) — selected by feature flag, currently both exist (this is the in-flight one).
3. Composite — uniform handling of single + group objects
Form in Flux
The Glob system is a textbook Composite: when the user selects multiple objects, the system synthesizes a composite glob that exposes the shared properties and dispatches commands to all members.
Canonical example
-
BendOpGlobwraps a singleBendOp(Src\UI\Bend\BendOpUI.csline 17). -
BendOpsGlobwraps multiple (Src\UI\Bend\BendOpUI.csline 661).
The composite is built automatically by the glob machinery — you only need to expose properties cooperatively. Properties that disagree across members typically fall back to a "mixed" sentinel.
Watch out for
-
Multi-edit semantics. When a property differs across selection, show the disagreement; don’t silently default to the first value. This is a common source of edit bugs.
-
Bulk command dispatch. Commands that mutate must be idempotent across members or wrapped in a single undo unit.
4. Factory method — Make everywhere
Form in Flux
Every non-trivial top-level model is constructed via a static Make factory that returns EResult. The constructor itself is private and assumes valid inputs.
Canonical examples
-
BendTech.Make (Part, BendMachine, EDoFlags = 0) → EResult. -
Machine.Get (int id) → Machine(cached factory). -
BendMachine.Get (string sid) → BendMachine.
Watch out for
-
The
Makeis the validation gate. Don’t add bypass paths that construct directly. -
The constructor often contains substantial logic (
BendTechconstructor is 250 lines). Read it as the spec for the object’s invariants.
5. Cascaded dictionary — the Sheaf
Form in Flux
A custom pattern: each settings holder (ISheafSite) has a local Dictionary<SH, object> and a parent. Reads walk up the chain.
Canonical example
Where: Src\Core\Settings\Sheaf.cs.
public interface ISheafSite {
string SHName { get; }
Dictionary<SH, object> SHDict { get; set; }
ISheafSite SHParent { get; }
}
A read for SH.RamVBend on a BendOp walks: BendOp.SHDict → BendTech.SHDict → BendMachine.SHDict → defaults.
Watch out for
-
Where you write matters. Setting a value at the op level shadows the tech and machine values. Setting at the machine level affects every tech that uses that machine.
-
The
SHenum is a closed registry. Adding a setting means extending the enum and (often) adding metadata.
6. Service locator — Hub
Form in Flux
Static class Hub exposes named delegate slots. The App layer fills them at startup. Core/UI code invokes them with null-safe calls.
Canonical example
Where: Src\Core\Hub.cs.
public static Func<string, string, IList<string>, int, bool, object, int> BigChooser;
public static Action CloseBlankMainWindow;
public static Func<string, bool, int> ClosePartByJobID;
Usage: Hub.ClosePartByJobID?.Invoke (jobId, force).
Watch out for
-
Always null-safe. A null slot is a valid headless state (Engine flavor).
-
Don’t add slots casually. The Hub is meant for genuine cross-layer plumbing, not as an alternative to passing arguments.
-
The pattern was introduced as a replacement for the older "Hub whiteboard" anti-pattern (see
Notes\Dev\1002.BuildingFlux.ftxlines 31—61). Don’t regress.
7. Observer — INotifyPropChanged + undo
Form in Flux
Flux has its own INotifyPropChanged (note the abbreviated name). Property setters typically route through BA.Set<T>(ref field, value) which performs three things at once:
-
Equality check (skip notification if unchanged).
-
Notification to subscribers.
-
Undo registration.
Canonical example
Throughout the domain code, e.g.:
public ISheafSite SHParent => Owner;
public bool BGAdjusted {
get => Get (EFlags.BGAdjusted);
set => Set (EFlags.BGAdjusted, value);
}
Watch out for
-
A direct field assignment (
mField = value) bypasses notification and undo. It’s a smell unless explicitly justified. -
The
Setpatterns differ between flag bits (Set (EFlags.X, value)) and full-typed fields (BA.Set (ref _field, value)). Match the surrounding code.
8. Template method — VModel.Draw
Form in Flux
The base class VModel defines the lifecycle (construction, going live, draw, detach). Subclasses override one method: Draw().
Canonical example
Where: Src\Base\Lux\VModel.cs (base) and any subclass like BendMountVM (Src\Core\Bend\BendVM.cs).
The override emits scene-graph nodes via Lux.DrawMesh, sets Lux.Layer and Lux.Mood, and uses using blocks for shading scopes.
Watch out for
-
All draw work must be deterministic and side-effect-free apart from the scene graph.
-
Don’t call back into model mutation from
Draw()— that’s a re-entrancy bug waiting to happen.
9. Migration via [PostLoad]
Form in Flux
A method annotated [PostLoad] runs after Au-deserialization. It uses AuType.ObjVersion to detect older shapes and migrate them forward.
Canonical example
BendSetup.cs lines 64—76 (see chapter 04 §9.5).
Watch out for
-
Migrations are forward-only.
-
Always check
AuType.ObjVersion < N(not==) — a file that sat through several versions still gets correctly upgraded. -
After migrating, the in-memory object should be indistinguishable from one created at version N.
10. Result / status enums instead of exceptions
Form in Flux
Predictable failure modes return an EResult rather than throw. Exceptions (CodingError) are reserved for invariant violations.
Canonical examples
-
BendTech.Make (...) → EResult. -
Part.DoFoldTo3D () → EResult. -
Tests assert
.Is (EResult.OK).
Watch out for
-
Adding a new failure mode? Audit
switch/Iscallers — the enum is exhaustively used. -
Don’t conflate user-error and coding-error in the same enum.
11. Visitor (light) — model-walking helpers
Form in Flux
There is no formal IVisitor interface, but model walks are concentrated in Model3 (Src\Core\Model\Model3.cs, 71 KB) and similar containers. The pattern is iteration with type filters:
foreach (var flex in model.Entities.OfType<E3Flex> ()) { ... }
Watch out for
-
Reflection-flavored
OfType<T>is fine for clarity; use index lookups (Entities[i] is T) when performance matters.
12. Memo — capture-and-restore
Form in Flux
When a tech is replaced, the previous state can be captured into a memo to allow recovery. Pattern: *Memo types adjacent to *Tech.
Canonical example
-
BendTechMemo(Src\Core\Bend\Ops\BendTechMemo.cs) constructed whenPart.BendTechis replaced.
Watch out for
-
A memo holds references; consider whether it should be a deep copy or a reference snapshot.
13. Singletons by convention
Form in Flux
Static instances named .It (e.g. StdBendCalc.It). This is a convention, not a runtime-enforced singleton — but every call site uses it.
Canonical example
public static readonly BToolPieceDescending It = new ();
Watch out for
-
Don’t add per-instance state to a
.Ittype.
14. The big anti-patterns (here be dragons)
14.1 The Hub whiteboard (legacy)
The previous incarnation of Hub was an object-typed dictionary used to pass arbitrary state across layers. Replaced by typed delegate slots — see §6. Any code resembling the old form is a refactor target.
14.2 Two crash engines
BendOpCrash.cs and NBendCrash.cs co-exist (chapter 02 §7.1). Treat any change to collision logic as a coordinated change to both, or you risk hidden divergence.
14.3 Constructors with substantial logic
The BendTech constructor is ~250 lines. This is intentional — the type’s invariants are enforced once, in one place — but it is unusual. Don’t refactor toward small constructors unless you have a good reason.
14.4 partial classes spanning multiple files
BendOp and others are split across files via partial. A Grep for a method may not find it in the file you’d guess. When editing, search the whole solution.
14.5 Static state that hides re-entrancy bugs
Part.Current, Part.Active, BendMachine.Default, the Hub slots — all static. A subtle bug class is "during a long operation, the user changes Part.Current". The auto-tooler guards against this with the Part.EFlags.AbortTooling check; other places may not.
14.6 Direct field assignment
A pure mField = value instead of BA.Set skips notification and undo. Sometimes intentional (private fixup), often a bug.
15. How to spot a pattern in unfamiliar code
A short checklist when you encounter a class for the first time:
-
Does it implement
ISheafSite? → it owns settings; reads cascade. -
Does it implement
INotifyPropChanged? → its property setters fire change events. -
Are there
*VMsiblings? → the rendering lives next to the model. -
Is there a
Makefactory? → constructor is private; use the factory. -
Is there a
Dump (TextWriter)method? → there is a golden-file test that compares against it. -
Is there a
[PostLoad]method? → there are persisted older versions in the wild. -
Does it have a
.Itstatic? → it’s a stateless helper / strategy.
16. Self-check
-
What pattern is
BendOpsGloban instance of? (§3) -
Why is
Hubnot a "service locator" in the textbook sense? (§6 — typed slots, null-safe, App-driven) -
What does
BA.Setdo that a plain field assignment doesn’t? (§7) -
Where is the
[PostLoad]migration ofBendSetup? (§9 + chapter 04 §9.5) -
Why are some constructors very long in Flux? (§14.3)
-
What’s the canonical way to choose between
StdBendCalcandTBSBendCalc? (§2 + chapter 02 §7.5)
17. If you take just one thing from this chapter
Patterns in Flux are pragmatic, not idealized. The Sheaf cascade, the Make factory, the Hub slots, and the BA.Set helper are the four most pervasive idioms. Recognize them, and ~80% of unfamiliar code becomes immediately readable.
Last updated: 2026-05-04. Pattern attributions verified against Src\Core\Bend\BendCalc.cs, Src\Core\Hub.cs, Src\Core\Settings\Sheaf.cs, Src\Base\Lux\VModel.cs, Src\UI\Bend\BendOpUI.cs, and Notes\Dev\1002.BuildingFlux.ftx.