Polymorphism Over Conditionals: Replacing Giant Switch Statements with Strategies
Sprawling switch(shape.type) statements across your React codebase violate the Open/Closed Principle — adding a new shape type requires changing 12 files simultaneously. Polymorphic interfaces and the Strategy pattern allow you to add new shapes and tools without modifying a single existing line of code.
Frontend Object-Oriented Architecture
Polymorphism Over Conditionals: Replacing Giant Switch Statements with Strategies
Domain objects are autonomous state machines with enforced invariants — they are not passive bags of data passed between controller functions. The canvas studio's tool system is the most conditional-heavy part of the codebase. Every pointer event — onPointerDown, onPointerMove, onPointerUp — routes through a switch on activeTool. New tools mean new case branches. Shared sub-behaviors (snapping, grid alignment) get copy-pasted. The switch statement grows into the most-read, most-modified, most-feared file in the project.
Polymorphism eliminates the switch. Each tool is an object that implements a common interface. The canvas component calls activeTool.onPointerDown(event) without knowing whether it is talking to SelectTool, RectTool, or PenTool. Adding a new tool is adding a new class — zero changes to the canvas component, zero branches added, zero existing behavior touched.
1. The Switch Anti-Pattern: Growing Without Bounds
The canvas component at month four of development:
Four problems compound as tools are added:
- Shotgun surgery: adding
'lasso'tool requires editing three switch statements (Down,Move,Up) in one file - State leakage:
drawStart,isDrawing,penPointsare all in component state but only relevant to specific tools — they accumulate regardless of which tool is active - Untestable: testing rect-drawing behavior requires mounting the entire canvas component
- No reuse: snapping logic is copy-pasted between
rectandellipsecases
2. The ITool Strategy Interface
The Strategy Pattern defines a family of algorithms (tools), encapsulates each one, and makes them interchangeable. The canvas component depends only on the ITool interface — not on any concrete tool:
The ToolPointerEvent wrapper converts React's PointerEvent into a canvas-space event — the tools never see React types. CanvasDocument is injected per-event, not stored in the tool — tools are stateless regarding the document (they can hold their own drawing state).
3. Concrete Tool Implementations
3.1 SelectTool
3.2 RectTool
3.3 PenTool
4. The ToolController: Managing the Active Strategy
The ToolController manages which tool is active and delegates events to it:
5. The Canvas Component: Zero Conditionals
The React component now has no tool-specific logic whatsoever:
Adding a fifth tool (LassoTool) requires:
- Create
LassoTool.tsimplementingITool - Register it:
tools.registerTool(new LassoTool())
Zero changes to Canvas.tsx. Zero new conditionals. Zero changes to SelectTool, RectTool, or any existing tool.
6. Testing Tools in Isolation
Zero React, zero component mounting, zero act(). Each test is 5 lines and runs in under 1ms.
Summary
| Concept | Before (Switch) | After (Strategy) |
|---|---|---|
| Adding a new tool | Edit 3 switch statements in canvas component | Add one new class implementing ITool |
| Tool-specific state | Component state (drawStart, penPoints) leaks across all tools |
Private fields inside each tool class |
| Testing | Mount canvas component with @testing-library/react |
new RectTool() + call methods directly |
| Snapping/grid logic | Copy-pasted into each switch case | Shared utility called by any tool that needs it |
| OCP compliance | Closed? No — every new tool opens the canvas component | Closed — register new tool, zero existing changes |
What's Next
Part 8 applies all five SOLID principles through the frontend lens — showing how SRP manifests in React component design, OCP in the renderer architecture, LSP in tool substitutability, ISP in snapshot interfaces, and DIP in hook-to-domain wiring.
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.