Skip to main content
chaptertechnical-review

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​

PatternRecurring problem and smallest implementationFailure or reason not to use
DIFunction argument supplies account adapterGlobal resolver hides requirements
FactoryFunction chooses adapter from validated configFactory that merely renames a constructor
Abstract factorySupply a matched storage/transport familyIndependent adapters do not need a family
BuilderValidate a multistep request configuration before buildObject literal suffices for a few fields
Scoped singletonReuse a safe pool within one runtimeUser state leaks between requests
PrototypeClone a configured editor nodeShallow clone shares mutable child arrays
AdapterTranslate partner DTO to domain viewSilently dropping currency or timezone semantics
FacadeUpload operation coordinates validation/storage/progressFacade becomes an unrelated “god service”
DecoratorWrap a port with tracing or retryRetry duplicates non-idempotent writes
ProxyGate access or observe readsInvisible network or mutation behavior
CompositeTreat leaves and groups as editor nodesCycles break naive recursive traversal
BridgeVary notification content and delivery independentlyOne axis of variation needs no bridge
FlyweightShare immutable formatting metadataShared mutable formatting changes every node

Behavioral patterns in application work​

PatternConcrete selectionTest the boundary
ObserverStore subscribers track one subjectUnsubscribe releases listener
Publish–subscribeIndependent modules consume typed eventsDuplicate event and missing subscriber policy
StrategySwap sorting or validation algorithmSame input contract across implementations
CommandRepresent an editor actionExecute/undo preserves invariant
StatePermit actions by explicit workflow stateReject terminal-to-running transition
Chain of responsibilityOrdered request validation handlersA denial stops later side effects
MediatorCoordinate editor panels centrallyNo hidden circular messages
IteratorTraverse paged or custom collectionsCancellation closes iterator resources
MementoStore bounded undo snapshotsNew edit discards redo branch
Template methodFixed algorithm with controlled variationPrefer function composition if inheritance adds no value
VisitorAdd operations over a typed ASTExhaustive 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.