Pattern selection and dependency lifetimes
Why names are insufficient
Patterns name recurring tradeoffs. Use them when they clarify ownership or variation, not to turn every function into a class. Dependency injection supplies a dependency from outside; dependency inversion is a direction-of-dependency principle; inversion of control is broader; a container is optional. A React provider can deliver an injected value but does not define its lifetime or make it safe across requests.
Plain TypeScript first
accountService(identity, accounts) receives an identity and a narrow accounts port. Its operation enforces tenant ownership. Tests supply an in-memory adapter without mocking React or fetch. The composition root chooses the live adapter once at the correct boundary. Avoid container.get() throughout domain logic: that hides requirements and turns a local function into a service-locator client.
Use process scope for immutable configuration and safe shared pools, request scope for current identity, operation scope for transactions, and subscription scope for stream readers. A module-level mutable current user is not a safe singleton in SSR. Two concurrent requests can observe the same instance.
Creation and structural pattern catalog
| Pattern | Recurring problem and smallest implementation | Failure or reason not to use |
|---|---|---|
| DI | Function argument supplies account adapter | Global resolver hides requirements |
| Factory | Function chooses adapter from validated config | Factory that merely renames a constructor |
| Abstract factory | Supply a matched storage/transport family | Independent adapters do not need a family |
| Builder | Validate a multistep request configuration before build | Object literal suffices for a few fields |
| Scoped singleton | Reuse a safe pool within one runtime | User state leaks between requests |
| Prototype | Clone a configured editor node | Shallow clone shares mutable child arrays |
| Adapter | Translate partner DTO to domain view | Silently dropping currency or timezone semantics |
| Facade | Upload operation coordinates validation/storage/progress | Facade becomes an unrelated “god service” |
| Decorator | Wrap a port with tracing or retry | Retry duplicates non-idempotent writes |
| Proxy | Gate access or observe reads | Invisible network or mutation behavior |
| Composite | Treat leaves and groups as editor nodes | Cycles break naive recursive traversal |
| Bridge | Vary notification content and delivery independently | One axis of variation needs no bridge |
| Flyweight | Share immutable formatting metadata | Shared mutable formatting changes every node |
Behavioral patterns in application work
| Pattern | Concrete selection | Test the boundary |
|---|---|---|
| Observer | Store subscribers track one subject | Unsubscribe releases listener |
| Publish–subscribe | Independent modules consume typed events | Duplicate event and missing subscriber policy |
| Strategy | Swap sorting or validation algorithm | Same input contract across implementations |
| Command | Represent an editor action | Execute/undo preserves invariant |
| State | Permit actions by explicit workflow state | Reject terminal-to-running transition |
| Chain of responsibility | Ordered request validation handlers | A denial stops later side effects |
| Mediator | Coordinate editor panels centrally | No hidden circular messages |
| Iterator | Traverse paged or custom collections | Cancellation closes iterator resources |
| Memento | Store bounded undo snapshots | New edit discards redo branch |
| Template method | Fixed algorithm with controlled variation | Prefer function composition if inheritance adds no value |
| Visitor | Add operations over a typed AST | Exhaustive handling when a node type is added |
Observer usually subscribes directly to a subject; publish–subscribe introduces a channel. Neither magically guarantees delivery, ordering or transactional business effects. The editor lab implements bounded memento history, command-like edits, and remote conflict behavior; its tests exercise undo, redo and branch replacement.
Architectural styles and UI mechanisms
Layers, MVC, MVP and MVVM separate responsibilities differently; Flux constrains update direction. Clean/hexagonal architecture places business behavior behind ports; vertical slices group by capability. They can overlap. Start with a feature slice containing UI, operation and adapter; split shared infrastructure only when it has multiple real consumers. A BFF is a network/service boundary, and micro-frontends add deployment and version coordination that folders do not.
Composition, slots and headless primitives let behavior and presentation vary. Controlled components expose state to the caller; uncontrolled components retain DOM/local state. Compound components share a coordinated context. Render props and HOCs remain useful integration mechanisms but can obscure ownership if stacked indiscriminately. Hooks share logic, not automatically state. Polymorphism must preserve the semantics of the rendered element.
Exercise and expected answer
Implement one operation with plain DI, then the Effect version in the next lesson. Swap two adapters and run identical behavior tests. Demonstrate the bug caused by process-scoped identity, then repair the scope. Explain why a factory, provider and service locator are not interchangeable. A strong answer deletes at least one unjustified abstraction and names the lifecycle owner of every retained one.
Executable reference and sources
Implementation · Behavioral evidence · Primary source.
Run the projects and assessment. Human review is pending.