The Problem
Different item types (physical, digital, subscription) have different rules for tax, shipping, and discounts. Adding new operations (like a tax calculator) requires modifying every item class. The item hierarchy is stable, but the operations performed on it change frequently.
The Solution
The Visitor pattern separates operations from the item class hierarchy. Each item implements accept(visitor) for double dispatch. New operations are added by creating new visitor classes, without touching existing item code.
Structure
- Visitor (interface
OrderVisitor) — DeclaresvisitPhysicalItem(),visitDigitalItem(),visitSubscriptionItem(). - Concrete Visitors —
TaxCalculatorVisitor,ShippingCostVisitor,DiscountVisitor. - Element (interface
OrderItemElement) — Declaresaccept(visitor). - Concrete Elements —
PhysicalItem,DigitalItem,SubscriptionItem.
Implementation
Three visitors compute different aspects of an order:
- TaxCalculatorVisitor — Physical: 10% tax, Digital: 5%, Subscription: 8%.
- ShippingCostVisitor — Physical: $5.99/item, Digital: $0, Subscription: $0.
- DiscountVisitor — Physical: 0%, Digital: 15% off, Subscription: 20% off.
export interface OrderVisitor {
visitPhysicalItem(item: { name: string; price: number; quantity: number; weight?: number }): number;
visitDigitalItem(item: { name: string; price: number; quantity: number }): number;
visitSubscriptionItem(item: { name: string; price: number; quantity: number }): number;
} NestJS Integration
The visitors are decorated with @Injectable() and registered as providers. The controller injects all three visitors and creates item elements from the request body. Each item’s accept() method performs double dispatch, calling the appropriate visit* method on the visitor. This keeps the NestJS layer thin — the controller orchestrates, the visitors compute.
When to Use
- You need to perform many unrelated operations on objects in a structure and want to avoid polluting their classes.
- The element class hierarchy is stable but operations change frequently.
- You want to accumulate results across an object structure.
When NOT to Use
- The element hierarchy changes frequently — each new element type requires updating all visitors.
- The operations are closely related to the elements’ data, making them natural methods on the element classes.
Related Patterns
Composite
Compose objects into tree structures to represent part-whole hierarchies.
Iterator
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Strategy
Define a family of algorithms, encapsulate each one, and make them interchangeable.