Rethinking GRASP (General Responsibility Assignment Software Patterns), SOLID (Single responsibility, Openβclosed, Liskov substitution, Interface segregation, Dependency inversion), GoF (Gang of Four) patterns, for Frontend (browsers) & Backend (node.js, other runtimes) development with JavaScript and TypeScript
- π§© Patterns
- π’ GoF patterns for Node.js and JavaScript (seminar fragment)
- π Creational patterns
- Abstract factory β creates related objects belonging to one family without specifying their concrete classes, e.g., UI components for different platforms.
- Builder β step-by-step assembly of a complex configurable object, often using chaining, e.g., Query Builder or Form Generator.
- Factory β function or method that creates objects using different techniques: assembling from literals and methods, mixins, setPrototypeOf.
- Factory Method β chooses the correct abstraction to create an instance; in JavaScript, this can be implemented using
if,switch, or selection from a collection (dictionary). - Prototype β creates objects by cloning a prepared instance to save resources (not to be confused with Prototype-programming, which is closer to Flyweight).
- Flyweight β saves memory allocation by sharing common state among multiple instances.
- Singleton β provides global access to a single instance; often considered an anti-pattern, easiest implemented via ESM/CJS module caching exported refs.
- Object Pool β reuses pre-created objects to save resources during frequent creation and destruction.
- π€ Structural patterns
- Adapter β converts an incompatible interface into a compatible one, enabling third-party component usage without altering its code; can even transform a function contract into an object or vice versa.
- Wrapper β function wrapper that delegates calls and adds behavior; a specialized case of Adapter.
- Boxing β wraps primitives into object types to add methods or unify interfaces, e.g., narrowing
StringtoAddressString. - Decorator β dynamically extends behavior without inheritance, typically via composition and declarative syntax, effectively adding metadata.
- Proxy β controls access to an object by intercepting calls, reads, and writes; useful for lazy initialization, caching, and security; can be implemented via GoF or native JavaScript Proxy.
- Bridge β separates two or more abstraction hierarchies via composition or aggregation, allowing them to evolve independently.
- Composite β implements a common interface to uniformly handle individual objects and their tree structures, e.g., DOM or file systems.
- Facade β simplifies access to a complex system, providing a unified and clear interface, hiding and protecting internal complexity.
- Flyweight β saves memory allocation by sharing common state among multiple instances.
- β‘ Behavioral patterns
- Chain of Responsibility β passes control through a chain of handlers, selecting a responsible one; all handlers can read, but only one will modify.
- Middleware β handler chain similar to CoR, but each can modify state and pass control to the next one, potentially leading to race conditions and conflicts.
- Command β encapsulates an action (execution request) and parameters into an object, allowing queuing, cancellation, repetition, etc.
- Interpreter β implements a DSL language (Domain Specific Language) or parses expressions into AST (Abstract Syntax Tree) for interpretation.
- Iterator β sequentially traverses collections or streams element-by-element without exposing all data; JavaScript provides built-in Iterator and AsyncIterator.
- Mediator β optimizes communication between N components, centralizing interaction to reduce coupling from
N*(N-1)/2down toN. - Memento β saves and restores snapshots of an object's state without direct access to its internal state.
- Observable β notifies subscribers about changes to an object's state via Events:
- EventEmitter for Node.js: Observable + listener
- EventTarget for Web API: EventTarget + Event (CustomEvent) + listener
- Signals
- State β implements a Finite State Machine (FSM) where methods represent transitions, and state is composed into abstraction and switched during transitions.
- Strategy β selects interchangeable behavior at runtime from a collection of implementations: functions, objects, or classes
- Template method β defines algorithm steps, allowing subclasses to override individual steps while defaulting to the superclass behavior.
- Visitor β adds operations to objects without altering their classes, separating structure and behavior into distinct abstractions.
- Revealing Constructor β changes behavior without inheritance, injecting functionality into constructors via functions or objects describing the behavior.
- Actor β Encapsulates state and behavior, communicating asynchronously via message passing and processing messages in a queue. Ensures thread-safe and async-safe concurrent operations by isolating actor state.
- Reactor (event-loop) - Handles concurrent events synchronously by adding them to queue and dispatching them to registered handlers. Implements event-driven async processing on the top of the sync one; commonly used in I/O-bound systems.
- Proactor - Event loop where operations started by user-land code but completed by an external agent (for example I/O subsystem), which then triggers a completion handler when the operation finishes (returning data to callback).
- Service Locator
- ποΈ Data access patterns
- Transaction Script β a procedural pattern where each business operation is implemented as a function or script that coordinates logic, data access, and side effects. Its purpose is to keep logic straightforward and centralized, ideal for simple applications or service-layer orchestration without requiring full domain modeling.
- Pattern SAGA β a distributed transaction pattern where a long-running business process is split into a sequence of local transactions, each with a compensating action in case of failure. It exists to avoid distributed locking.
- Unit of Work β a transactional boundary pattern that tracks changes to business objects and coordinates persistence as a single atomic operation in ORMs or Repository layers to encapsulate all work in a transaction.
- Table Module β a pattern where all domain logic related to a database table is encapsulated in a single class or module, treating rows as simple data.
- Value Object β an immutable, self-validating object that represents a concept in the domain without identity, used to express domain constraints and logic by value rather than reference. It exists to model descriptive attributes, ensure consistency, and support value-based equality in a type-safe, intention-revealing way.
- Null Object β an object that implements a standard interface but provides neutral, do-nothing behavior, designed to avoid null checks, simplify control flow, and ensure polymorphic safety. It exists as a safe default substitute to eliminate conditionals and guard clauses in client code.
- Active Record β domain object encapsulating a database record, providing methods to directly perform CRUD operations (create, read, update, delete) and domain-specific queries on itself.
- Data access object (DAO) β abstraction defining an interface to persist and retrieve domain objects, isolating domain logic from specific storage implementations.
- Data transfer object (DTO) β an anemic object (just plain data) carrier without domain behavior, designed explicitly for transferring structured data across application boundaries, layers, modules, or subsystems.
- Data Access Layer (DAL) β a layer abstracting access to multiple DAOs or raw data sources. Can be represented as Facade pattern. Often includes transformations.
- Repository β domain-centric abstraction for data access that returns domain entities, not raw data or DTOs.
- Other patterns: Template method, Actor, State, Memento
- π§© GRASP patterns
- GRASP Overview
- GRASP Part 1: Information expert, Creator, Low coupling, High cohesion
- GRASP Part 2: Protected variations, Indirection, Pure fabrication, Polymorphism, Controller
- Information expert
- Low coupling
- High cohesion
- Pure fabrication
- Real code examples
- Information Expert - assign responsibilities to those abstractions that have the necessary data. Related: Encapsulation, Cohesion, Coupling, Information hiding, SOLID: SRP, SoC.
- Creator - if one abstraction writes, reads, aggregates, uses, or is tightly coupled with another, then it should create and initialize that other abstraction. Related: Information Expert, GoF Creational patterns.
- Controller - contains use-case scenarios for processing external I/O requests from the UI, API, or event bus and delegates execution to other abstractions. Related: GoF Command, Facade, Layers, Pure Fabrication.
- Low Coupling - each abstraction minimally depends on the implementation details of others and contains a minimum of "knowledge" (calls). Provides stability, ease of testing, and maintenance. Related: High Cohesion, Controller, Indirection, DIP, DI, IoC, Revealing Constructor, Facade, Mediator, Observer, Strategy, State, Bridge, Adapter, Proxy.
- High Cohesion - all internal elements of an abstraction are tightly bound by a common purpose and "know" each other's contracts, working together to solve a single, specific problem. Such abstractions are easy to understand, test, and maintain. Related: Low Coupling, Information Expert, Composite, Facade, Adapter. High coupling within a module and low coupling between modules ensure system stability.
- Polymorphism - using dynamic dispatch, abstractions select behavior and delegate actions to objects with a common interface instead of explicitly branching by type. Related: GoF Strategy, Adapter, Creator, Command, State, Bridge, Template Method, Visitor, Factory Method, Proxy.
- Pure Fabrication - artificial abstractions that are not domain-specific but serve structural, architectural, and technical needs. Related: SRP, ISP, GoF: Facade, Adapter, Observer, Command, Mediator, Repository, Service. Examples: EventEmitter, Stream, Connection, Promise, Error.
- Indirection - a mediator for implementing loose coupling between components. Related: GoF Mediator, Facade, Observer, Service Layer, API Gateway, Message Broker, Event Bus.
- Protected Variations - protects abstractions from change by extracting interactions to a fixed interface, only through which interactions between abstractions are possible. Related: Interface, Contract Programming, Generics, OCP, DIP, DI, IoC, GoF: Strategy, Bridge, Abstract Factory, Factory Method, Adapter, Proxy, Facade.
- π§© SOLID Patterns
- π’ Intro video: SOLID for Node.js and Javascript
- SOLID Interview questions
- Single responsibility principle
- Open/closed principle
- Liskov substitution principle
- Interface segregation principle
- Dependency inversion principle
- Single responsibility principle - a class should have only one reason to change. "A module should be responsible for one and only one actor."
- Open-closed principle - abstractions (classes, types, etc.) should be open for extension but closed for modification.
- Liskov substitution principle - functions that use a base type should be able to use subtypes of the base type without knowing it.
- Interface segregation principle - many interfaces specifically designed for clients are better than one general-purpose interface.
- Dependency inversion principle - depend upon abstractions, not concretes.