B
🎭 Part 4 of 4 — Behavioral II + Full Review

GoF Behavioral II
& Complete Review

The final 5 behavioral patterns with production examples, framework usage, and deep Q&As — plus a comprehensive review: all 23 GoF patterns at a glance, the complete Spring→Pattern mapping, key anti-patterns to avoid, and architect-level interview questions.

Iterator — fail-fast vs fail-safe, Stream API Mediator — DispatcherServlet, Chat room Memento — Undo, Spring Batch checkpoint Visitor — AST, Jackson serializers, Report generation Interpreter — SpEL, Spring Security expressions All 23 GoF Patterns Summary Table Spring→Pattern Master Map Anti-Patterns to Avoid
5
New Patterns
23
GoF Total Review
20+
Interview Q&As
4/4
Series Complete
Day 19 · W3 Iterator Pattern Behavioral
Intent

Provide a way to sequentially access elements of a collection without exposing its internal structure. The iterator decouples traversal logic from the collection itself. The client never needs to know if it's traversing an array, linked list, tree, or database cursor.

Java's built-in Iterator: Every Java collection implements Iterable<T>, which provides an Iterator<T>. The enhanced for-loop (for (Order o : orders)) is syntactic sugar that calls iterator() and loops with hasNext()/next(). You use Iterator every single day.

Fail-fast vs Fail-safe — the critical interview topic: Fail-fast iterators throw ConcurrentModificationException if the collection is modified during iteration. Fail-safe iterators operate on a snapshot copy — modifications don't affect the ongoing iteration.

JavaCustom Iterator + Fail-Fast vs Fail-Safe + Stream API as Functional Iterator
// CUSTOM ITERATOR: date range iterator — no collection stored in memory
public class DateRangeIterator implements Iterator<LocalDate> {
    private LocalDate current;
    private final LocalDate endInclusive;

    public DateRangeIterator(LocalDate start, LocalDate end) {
        this.current      = start;
        this.endInclusive = end;
    }
    @Override
    public boolean hasNext() { return !current.isAfter(endInclusive); }
    @Override
    public LocalDate next() {
        if (!hasNext()) throw new NoSuchElementException();
        LocalDate result = current;
        current = current.plusDays(1);
        return result;
    }
}

// Make it Iterable for enhanced for-loop and Stream support
public class DateRange implements Iterable<LocalDate> {
    private final LocalDate start, end;
    public DateRange(LocalDate start, LocalDate end) { this.start=start; this.end=end; }

    @Override
    public Iterator<LocalDate> iterator() { return new DateRangeIterator(start, end); }

    // Convert to Stream — Spliterator is the Stream-aware iterator
    public Stream<LocalDate> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
}

// Usage: clean as any built-in collection
var range = new DateRange(LocalDate.of(2025, 1, 1), LocalDate.of(2025, 12, 31));
for (LocalDate d : range) { /* process each date */ }
range.stream().filter(LocalDate::isLeapYear).forEach(System.out::println);

// FAIL-FAST vs FAIL-SAFE — the interview distinction
// ─────────────────────────────────────────────────────────────────────────

// FAIL-FAST: ArrayList, HashMap, HashSet, LinkedList
// Uses a modCount field — any structural modification increments modCount.
// Iterator checks modCount on every next() call — mismatch → CME
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
for (String s : list) {
    if (s.equals("b")) list.remove(s); // ❌ ConcurrentModificationException!
}

// CORRECT: use Iterator.remove() — modCount-aware
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("b")) it.remove(); // ✅ safe — iterator updates modCount
}

// ALSO CORRECT in Java 8+: removeIf (uses iterator.remove() internally)
list.removeIf(s -> s.equals("b"));           // ✅ cleanest

// FAIL-SAFE: CopyOnWriteArrayList, ConcurrentHashMap, ConcurrentSkipListMap
// Iterator works on a SNAPSHOT of the collection at iteration start.
// Modifications to the original don't affect ongoing iteration — no CME.
// Trade-off: snapshot takes memory; you might see stale data during iteration.
List<String> cowList = new CopyOnWriteArrayList<>(List.of("a", "b", "c"));
for (String s : cowList) {
    cowList.add("d"); // ✅ no CME — iterator uses snapshot from before this add
}

// STREAM API — Iterator as functional pipeline (lazy evaluation)
// Stream's Spliterator is a parallel-aware Iterator — supports split for parallel execution
List<Order> orders = repo.findAll();

// Lazy pipeline — nothing executes until terminal operation
BigDecimal totalRevenue = orders.stream()               // creates Spliterator
    .filter(o -> o.getStatus() == OrderStatus.DELIVERED)  // lazy intermediate
    .filter(o -> o.getCreatedAt().getYear() == 2025)      // lazy intermediate
    .map(Order::getTotal)                                 // lazy intermediate
    .reduce(BigDecimal.ZERO, BigDecimal::add);            // TERMINAL — triggers all above

// Spring Batch: ItemReader IS an Iterator
// chunk-oriented processing: reader.read() until null, process batch, write batch
@Bean
public JdbcCursorItemReader<Order> orderReader(DataSource ds) {
    return new JdbcCursorItemReaderBuilder<Order>()
        .dataSource(ds)
        .sql("SELECT * FROM orders WHERE status = 'PENDING' ORDER BY created_at")
        .rowMapper(new BeanPropertyRowMapper<>(Order.class))
        .build(); // reads one row at a time — Iterator over DB cursor
}
🔩 Where Iterator Is Used in Java Frameworks
Java Collections Framework
Every collection implements Iterable<T>. Enhanced for-loop calls iterator(). Iterator.remove() is the only safe way to remove during traversal.
Java Stream API (Spliterator)
Spliterator is Iterator + split support for parallel streams. Every stream source uses a Spliterator. StreamSupport.stream(spliterator, parallel) creates streams from custom iterables.
Spring Batch ItemReader
ItemReader.read() returns the next item or null when exhausted — a simplified Iterator. JdbcCursorItemReader, FlatFileItemReader, JpaPagingItemReader all implement this pattern.
Spring Data Slice / Page
Page<T> extends Slice<T> which extends Iterable<T>. Pagination IS an iterator over chunks. hasNext() / nextPageable() navigate through database result pages.
ResultSet (JDBC)
ResultSet.next() is a forward-only iterator over DB rows. JdbcTemplate handles the iteration loop; you provide the RowMapper for what to do with each row.
🎯 Interview Questions — Iterator
Fail-fast vs fail-safe iterators — what is the difference? Give examples of each.
Fail-fast: Detects structural modification during iteration and throws ConcurrentModificationException immediately. Uses modCount — each structural change (add, remove, clear) increments it. The iterator captures modCount at creation time; next() checks it on every call. Examples: ArrayList, HashMap, HashSet, LinkedList.
Fail-safe: Iterates on a snapshot taken at iterator creation. Modifications to the live collection don't throw — but you may miss newly added elements or see already-removed ones. Examples: CopyOnWriteArrayList, ConcurrentHashMap, CopyOnWriteArraySet.
Why "fail-fast" and not "thread-safe": Even single-threaded code causes CME if you modify the collection via list.remove() instead of iterator.remove(). Use removeIf() or collect-then-remove for clean bulk deletion.
What is ConcurrentModificationException? How do you safely remove elements during iteration?
ConcurrentModificationException is thrown when a fail-fast iterator detects that the underlying collection was structurally modified after the iterator was created — even in a single-threaded program.
Safe removal options:
  1. iterator.remove() — only safe removal during iteration. Updates modCount.
  2. list.removeIf(predicate) — cleanest, Java 8+, uses iterator internally.
  3. Collect then remove: List<X> toRemove = list.stream().filter(...).toList(); list.removeAll(toRemove);
  4. Use CopyOnWriteArrayList — but modifying during iteration creates a new copy, expensive.
Never call list.add() or list.remove() directly while iterating a fail-fast collection.
What is Spliterator and how does it differ from Iterator?
Spliterator (Splittable Iterator, Java 8) extends Iterator with two key additions: (1) trySplit() — splits the iteration space into two halves for parallel processing. (2) characteristics() — flags like SIZED, ORDERED, SORTED, DISTINCT, IMMUTABLE that the Stream API uses to optimize operations.
When you call list.parallelStream(), the stream framework calls trySplit() recursively to create sub-spliterators, each processed on a different ForkJoinPool thread.
Example: ArrayList's Spliterator splits by index — left half and right half. Custom iterators should implement Spliterator if parallel processing is needed.
Day 20 · W3 Mediator Pattern Behavioral
Intent

Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly. Objects communicate through the mediator instead of directly — reducing a many-to-many dependency web to many-to-one.

Mediator vs Facade: Facade = one-directional simplification (client → Facade → subsystems). Mediator = bidirectional coordination between peers (object A tells Mediator → Mediator decides what to tell B, C, D). Facade hides subsystems from external clients; Mediator reduces coupling between peers.

Spring's DispatcherServlet IS a Mediator. Controllers, HandlerMappings, ViewResolvers, and MessageConverters don't talk to each other directly — they all interact through DispatcherServlet. It orchestrates the entire request handling lifecycle.

JavaChat Room Mediator + Spring MVC DispatcherServlet as Mediator
// MEDIATOR INTERFACE
public interface ChatMediator {
    void sendMessage(String message, ChatUser sender);
    void sendPrivate(String message, ChatUser sender, ChatUser recipient);
    void register(ChatUser user);
    void unregister(ChatUser user);
}

// CONCRETE MEDIATOR — contains all the routing logic
public class ChatRoom implements ChatMediator {
    private final Map<String, ChatUser> users = new ConcurrentHashMap<>();
    private final List<ChatMessage>     history = new CopyOnWriteArrayList<>();

    @Override
    public void register(ChatUser user) {
        users.put(user.getName(), user);
        broadcast("[System] " + user.getName() + " joined the room", null);
    }

    @Override
    public void sendMessage(String msg, ChatUser sender) {
        // Mediator decides who receives the message — senders don't know about receivers
        history.add(new ChatMessage(sender.getName(), msg, Instant.now()));
        users.values().stream()
            .filter(u -> !u.equals(sender))  // don't echo back to sender
            .filter(ChatUser::isOnline)
            .forEach(u -> u.receive(sender.getName() + ": " + msg));
    }

    @Override
    public void sendPrivate(String msg, ChatUser sender, ChatUser recipient) {
        if (recipient.isOnline()) {
            recipient.receive("[PM from " + sender.getName() + "] " + msg);
        } else {
            sender.receive("[System] " + recipient.getName() + " is offline");
        }
    }

    private void broadcast(String msg, ChatUser exclude) {
        users.values().stream()
            .filter(u -> !u.equals(exclude))
            .forEach(u -> u.receive(msg));
    }

    @Override
    public void unregister(ChatUser u) {
        users.remove(u.getName());
        broadcast("[System] " + u.getName() + " left the room", null);
    }
}

// COLLEAGUE — users talk TO the mediator, not to each other directly
public class ChatUser {
    private final String       name;
    private final ChatMediator mediator; // knows mediator, NOT other users
    private       boolean      online = true;

    public void send(String message) {
        mediator.sendMessage(message, this); // routes through mediator
    }
    public void pm(ChatUser to, String msg) {
        mediator.sendPrivate(msg, this, to); // mediator handles delivery logic
    }
    public void receive(String msg) { System.out.println("[" + name + "] " + msg); }
    // Alice never calls bob.receive() directly — all via ChatRoom mediator
}

// HOW SPRING'S DispatcherServlet IS a Mediator:
//
// Components: HandlerMapping, HandlerAdapter, ViewResolver, ExceptionResolver
// Mediator: DispatcherServlet
//
// Without Mediator: every component would need to know about all others.
// HandlerMapping would need to know about HandlerAdapters.
// HandlerAdapters would need to call ViewResolvers directly.
// → N×N coupling web.
//
// With DispatcherServlet as Mediator:
// 1. DispatcherServlet receives HTTP request
// 2. Asks HandlerMapping: "who handles GET /orders?" → returns handler + interceptors
// 3. Asks HandlerAdapter: "can you execute this handler?" → adapter.handle()
// 4. Adapter returns ModelAndView
// 5. DispatcherServlet asks ViewResolver: "render this view name" → View
// 6. View renders response
// → Each component is M:1 with DispatcherServlet, not M:N with each other.

// SPRING WEBFLUX: WebHandler plays the same Mediator role in reactive stack
🔩 Where Mediator Is Used in Java Frameworks
Spring DispatcherServlet
Central Mediator between HandlerMappings, HandlerAdapters, ViewResolvers, ExceptionResolvers. Each component is decoupled — communicates only with DispatcherServlet, not with each other.
Spring WebSocket / STOMP Broker
The STOMP message broker mediates between WebSocket clients. One client sends to /topic/orders; the broker fans out to all subscribers. Clients don't know about each other.
Spring Integration — MessageChannel
MessageChannel is the Mediator between message producers and consumers. Components publish to channels; channel routes to appropriate handlers. Zero direct coupling between producers and consumers.
Java Executor (ThreadPool)
ExecutorService mediates between task submitters and worker threads. Submitters call executor.submit(task); the executor decides which thread runs it and when. Submitter never talks to the thread directly.
MVC Pattern itself
The Controller in MVC is a Mediator between View and Model. View events go to Controller; Controller updates Model and tells View what to display. View and Model never interact directly.
🎯 Interview Questions — Mediator
Mediator vs Facade — both centralize communication. What is the difference?
Facade: One-directional. External client → Facade → subsystems. The subsystems don't know about the Facade — the Facade calls them. The Facade simplifies access for external clients but doesn't reduce coupling between subsystems.
Mediator: Bidirectional. Peers → Mediator → other peers. All colleagues know the mediator and communicate through it. Reduces coupling BETWEEN peers. No peer calls another peer directly. The mediator contains the interaction logic.
Example distinction: OrderFacade hides inventory/payment/shipping from the controller — the controller doesn't know these exist. ChatRoom mediates between users — users know the ChatRoom exists but not other users. Facade is about simplifying the API; Mediator is about reducing inter-object coupling.
How does DispatcherServlet implement Mediator pattern?
Without DispatcherServlet, Spring MVC components would need direct references to each other — HandlerMapping would need to know which adapter to use for each handler type, adapters would need to call view resolvers, etc. N² coupling.
With DispatcherServlet: (1) All HandlerMappings registered with DS at startup. (2) All HandlerAdapters registered. (3) All ViewResolvers registered. (4) On each request: DS calls getHandler() on each mapping, gets the match, calls getHandlerAdapter(handler), calls adapter.handle(), gets ModelAndView, calls resolvers to render. Each component is M:1 with DS, zero M:N coupling between components. Adding a new type of handler = register new Adapter — DS routing logic is unchanged.
Day 21 · W3 Memento Pattern Behavioral
Intent

Capture and externalise an object's internal state so that the object can be restored to this state later, without violating encapsulation. The memento is an opaque state snapshot — only the originator knows how to interpret it.

Three participants: (1) Originator — the object whose state is saved. Creates and uses mementos. (2) Memento — stores the internal state. Opaque to everyone except Originator. (3) Caretaker — holds mementos for safekeeping. Never inspects or modifies the memento content.

Memento vs Command for undo: Command saves the delta (what changed + how to reverse it). Memento saves the full snapshot. Use Command when operations are discrete and reversible individually. Use Memento when full-state snapshots are cheaper or needed (e.g., game saves, form draft states, wizard step history).

JavaOrder Draft Memento + Spring Batch Checkpoint/Restart
// MEMENTO — immutable snapshot of Originator's state (opaque to Caretaker)
public final class OrderDraftMemento {
    // All fields private — Caretaker cannot read them
    private final List<OrderItem> items;
    private final Address        shippingAddress;
    private final String         couponCode;
    private final PaymentMethod  paymentMethod;
    private final Instant        savedAt;

    // Package-private constructor — only OrderDraft (Originator) in same package can create
    package OrderDraftMemento(List<OrderItem> items, Address addr,
                               String coupon, PaymentMethod pm) {
        this.items          = List.copyOf(items); // defensive copy — immutable snapshot
        this.shippingAddress = addr;
        this.couponCode      = coupon;
        this.paymentMethod   = pm;
        this.savedAt         = Instant.now();
    }
    // Only Originator can access these via package-private getters
    List<OrderItem> getItems()          { return items; }
    Address         getShippingAddress() { return shippingAddress; }
    String          getCouponCode()      { return couponCode; }
    PaymentMethod   getPaymentMethod()  { return paymentMethod; }
    public Instant  getSavedAt()        { return savedAt; } // public — Caretaker can show timestamp
}

// ORIGINATOR — creates and restores from mementos
public class OrderDraft {
    private List<OrderItem> items          = new ArrayList<>();
    private Address        shippingAddress;
    private String         couponCode;
    private PaymentMethod  paymentMethod;

    public void addItem(OrderItem item)           { items.add(item); }
    public void setAddress(Address addr)          { this.shippingAddress = addr; }
    public void applyCoupon(String code)          { this.couponCode = code; }
    public void setPaymentMethod(PaymentMethod pm){ this.paymentMethod = pm; }

    // SAVE: create a memento (snapshot of current state)
    public OrderDraftMemento save() {
        return new OrderDraftMemento(items, shippingAddress, couponCode, paymentMethod);
    }

    // RESTORE: bring back a previous state from a memento
    public void restore(OrderDraftMemento memento) {
        this.items          = new ArrayList<>(memento.getItems());
        this.shippingAddress = memento.getShippingAddress();
        this.couponCode      = memento.getCouponCode();
        this.paymentMethod   = memento.getPaymentMethod();
    }
}

// CARETAKER — stores mementos, never inspects their content
@Service
public class OrderDraftHistory {
    private final Deque<OrderDraftMemento> history = new ArrayDeque<>();
    private static final int MAX_SNAPSHOTS = 10;

    public void save(OrderDraft draft) {
        history.push(draft.save()); // save current state
        if (history.size() > MAX_SNAPSHOTS) history.removeLast(); // cap history
    }

    public void undo(OrderDraft draft) {
        if (history.isEmpty()) return;
        draft.restore(history.pop()); // restore previous state
    }

    // Caretaker can see metadata (savedAt) but not content
    public List<Instant> getHistoryTimestamps() {
        return history.stream().map(OrderDraftMemento::getSavedAt).toList();
    }
}

// SPRING BATCH: uses Memento pattern for checkpoint/restart
// ExecutionContext IS the Memento:
//   - Stores progress state: {"current.line.count": 5000, "last.id": "abc123"}
//   - Persisted to DB after every chunk commit
//   - On restart: Spring reads ExecutionContext, passes to reader — reader resumes from checkpoint
//   - This IS Memento: JobExecution (Caretaker) stores ExecutionContext (Memento)
//     for the Step (Originator) to restore from on restart

@Component
public class CheckpointingItemReader extends AbstractItemStreamItemReader {
    private int currentIndex = 0;

    @Override
    public void open(ExecutionContext ctx) {
        // RESTORE from memento if restarting
        if (ctx.containsKey("reader.index")) {
            currentIndex = ctx.getInt("reader.index"); // restore from checkpoint
            log.info("Resuming from checkpoint: index={}", currentIndex);
        }
    }

    @Override
    public void update(ExecutionContext ctx) {
        // SAVE: take snapshot of current progress after each chunk
        ctx.putInt("reader.index", currentIndex); // caretaker (Spring) persists this
    }

    @Override
    public Order read() {
        return currentIndex < data.size() ? data.get(currentIndex++) : null;
    }
}
🔩 Where Memento Is Used in Java Frameworks
Spring Batch ExecutionContext
ExecutionContext is the Memento. Persisted to DB after every chunk. On job restart, Spring restores it — reader uses it to skip already-processed records. Classic checkpoint/restart.
JPA First-Level Cache (dirty checking)
Hibernate's persistence context takes a snapshot of each loaded entity's state at load time. On flush, it compares current state with snapshot to detect changes — generating only UPDATE statements for changed fields. The snapshot IS a Memento.
DB Savepoints
connection.setSavepoint("beforeRiskyOp") creates a Memento of transaction state. connection.rollback(savepoint) restores it. Partial rollback within a single transaction — textbook Memento.
Java Serialization
Serializing an object creates a byte-array Memento of its state. Deserializing restores it. Used for session clustering (storing HTTP session state in Redis/DB across app restarts).
🎯 Interview Questions — Memento
Memento vs Command for undo — when to use each?
Command undo: Stores the delta and reverse operation. AddItemCommand.undo() removes the specific item added. Memory-efficient (stores only what changed). Complex to implement for operations with many side effects.
Memento undo: Stores a full state snapshot. Restore by copying the snapshot back. Simpler to implement — no need to compute the inverse of every operation. Memory-intensive for large objects with many history points.
When to use Memento: (1) Object state is complex and hard to reverse incrementally. (2) You need arbitrary rollback (not just one step back). (3) The "undo" is just replacing the whole state (text editors, game saves, wizard steps).
When to use Command: (1) Operations are discrete and individually reversible. (2) Memory is constrained. (3) You need an audit log (Command objects describe what happened). In practice: many systems use both — Command for recent granular undo, Memento for full checkpoints.
How does Hibernate dirty checking implement Memento? What are the implications?
When Hibernate loads an entity, it stores a deep copy (snapshot) of its state in the persistence context — this is the Memento. The entity is the Originator; the persistence context is the Caretaker.
At flush time, Hibernate compares the entity's current field values with the snapshot. Only fields with different values generate UPDATE SQL statements — this is the "dirty checking" mechanism. It's efficient because it generates minimal SQL, but it has a memory cost: every loaded entity has a full copy stored in the session.
Implication: Loading 10,000 entities in a session → 20,000 object graphs in memory (entity + snapshot). For bulk read operations where you don't need to update, use @QueryHint(name = "org.hibernate.readOnly", value = "true") to skip snapshot creation, halving memory usage. Or use projections instead of entities for read-only queries.
Day 22 · W4 Visitor Pattern Behavioral
Intent

Represent an operation to be performed on elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates. Add operations to a class hierarchy without modifying it.

Double dispatch: Visitor uses double dispatch — the operation executed depends on both the visitor type AND the element type. element.accept(visitor) dispatches to the correct element class, then visitor.visit(this) dispatches to the correct visitor method.

When to use: You have a stable class hierarchy (shapes, AST nodes, report elements) but frequently need new operations on it. Without Visitor: each new operation requires modifying every class. With Visitor: new operation = new Visitor class. Existing hierarchy unchanged.

JavaReport Visitor — Tax + Export + Audit operations on order hierarchy
// ELEMENT INTERFACE — accept is the hook for double dispatch
public interface OrderElement {
    void accept(OrderVisitor visitor);
}

// CONCRETE ELEMENTS — stable hierarchy, rarely changes
public class OrderItem implements OrderElement {
    private final String     sku;
    private final BigDecimal unitPrice;
    private final int        quantity;
    private final String     category; // electronics, food, clothing...

    @Override
    public void accept(OrderVisitor visitor) {
        visitor.visit(this); // dispatch to visitor.visit(OrderItem) — double dispatch
    }
    // getters...
}

public class ShippingCharge implements OrderElement {
    private final BigDecimal amount;
    private final String     method; // STANDARD, EXPRESS, SAME_DAY

    @Override
    public void accept(OrderVisitor visitor) { visitor.visit(this); }
}

public class Discount implements OrderElement {
    private final BigDecimal amount;
    private final String     type; // COUPON, LOYALTY, SEASONAL

    @Override
    public void accept(OrderVisitor visitor) { visitor.visit(this); }
}

// VISITOR INTERFACE — one visit method per element type
public interface OrderVisitor {
    void visit(OrderItem      item);
    void visit(ShippingCharge charge);
    void visit(Discount       discount);
}

// VISITOR 1: Tax calculator — new operation, zero changes to elements
public class TaxCalculatorVisitor implements OrderVisitor {
    private BigDecimal totalTax = BigDecimal.ZERO;

    @Override
    public void visit(OrderItem item) {
        // Tax rules vary by category
        BigDecimal rate = switch (item.getCategory()) {
            case "electronics" -> BigDecimal.valueOf(0.18); // 18% GST
            case "food"        -> BigDecimal.valueOf(0.05); // 5%
            case "clothing"    -> BigDecimal.valueOf(0.12); // 12%
            default           -> BigDecimal.valueOf(0.0);
        };
        totalTax = totalTax.add(item.getUnitPrice()
            .multiply(BigDecimal.valueOf(item.getQuantity()))
            .multiply(rate));
    }
    @Override
    public void visit(ShippingCharge charge) { /* shipping not taxed */ }
    @Override
    public void visit(Discount discount) { /* discounts reduce taxable base — simplified */ }

    public BigDecimal getTotalTax() { return totalTax; }
}

// VISITOR 2: JSON export — completely different operation, zero element changes
public class JsonExportVisitor implements OrderVisitor {
    private final StringBuilder json = new StringBuilder("[");
    private       boolean        first = true;

    @Override
    public void visit(OrderItem item) {
        comma();
        json.append(String.format("""
            {"type":"item","sku":"%s","price":%s,"qty":%d,"category":"%s"}""",
            item.getSku(), item.getUnitPrice(), item.getQuantity(), item.getCategory()));
    }
    @Override
    public void visit(ShippingCharge c) {
        comma(); json.append(String.format("""{"type":"shipping","amount":%s}""", c.getAmount()));
    }
    @Override
    public void visit(Discount d) {
        comma(); json.append(String.format("""{"type":"discount","amount":%s}""", d.getAmount()));
    }
    public String getJson() { return json.append("]").toString(); }
    private void comma() { if (!first) json.append(","); first = false; }
}

// Usage: same element hierarchy, two completely different operations
Order order = /* ... */;
List<OrderElement> elements = order.getElements(); // items + shipping + discounts

TaxCalculatorVisitor taxVisitor = new TaxCalculatorVisitor();
elements.forEach(e -> e.accept(taxVisitor));
BigDecimal tax = taxVisitor.getTotalTax(); // result of visiting all elements

JsonExportVisitor jsonVisitor = new JsonExportVisitor();
elements.forEach(e -> e.accept(jsonVisitor));
String json = jsonVisitor.getJson(); // completely different result, same elements
🔩 Where Visitor Is Used in Java Frameworks
Jackson ObjectMapper (Serialization)
Jackson visits each field/property of your Java object. JsonSerializer<T> is a Visitor that writes each element to a JsonGenerator. Different serializers for Date, BigDecimal, enum, etc. — different visit behavior per type.
Spring BeanDefinitionVisitor
Visits all bean definitions at startup to resolve property placeholders (${property.name}). The BeanDefinition graph is the object structure; the visitor replaces placeholder strings with actual values.
Java Compiler API (AST visitors)
com.sun.source.tree.TreeVisitor visits each node type of the Abstract Syntax Tree (MethodTree, ClassTree, VariableTree). Used by annotation processors and static analysis tools (Checkstyle, SpotBugs).
JPA CriteriaQuery
JPA Criteria API builds an expression tree. Query execution visits each node — Predicate, Expression, Join — and generates the corresponding SQL. Each database dialect implements its own visitor that generates dialect-specific SQL.
Java NIO FileVisitor
Files.walkFileTree(path, fileVisitor) walks the directory tree calling visitFile(), preVisitDirectory(), postVisitDirectory(). Classic Visitor pattern for file system traversal.
🎯 Interview Questions — Visitor
What is double dispatch in Visitor? Why is it needed?
Java uses single dispatch — which method to call is determined at runtime based on the receiver object type only. visitor.visit(element) would use the compile-time type of element (e.g., OrderElement), not the runtime type (OrderItem). Visitor overloads would all call the same method.
Double dispatch solution: (1) First dispatch: element.accept(visitor) — dispatches based on element's runtime type (OrderItem's accept() is called). (2) Second dispatch: inside accept(), visitor.visit(this) — now this is statically known to be OrderItem, so visitor.visit(OrderItem) is called. Two runtime type lookups = double dispatch.
Without this, you'd need instanceof chains in every visitor, defeating the purpose.
Visitor vs Strategy — when to use each for adding operations?
Visitor: Object hierarchy is stable, but you add new operations frequently. Adding a new operation = new Visitor class. Best when hierarchy has many element types and operations are complex. Downside: adding a new Element type requires modifying ALL visitors.
Strategy: One algorithm varies — pick which version to use at runtime. No hierarchy of element types. Best when there's one variable dimension (pricing algorithm, sorting, auth method).
Rule: Visitor = multiple element types × multiple operations (matrix of behavior). Strategy = one context × one variable algorithm. If you think about it as a 2D table: Visitor fills the table (rows=elements, columns=operations); Strategy varies along one axis only.
Day 23 · W4 Interpreter Pattern Behavioral
Intent

Define a representation for a language's grammar along with an interpreter that uses the representation to interpret sentences in the language. Use when you need to interpret expressions in a simple custom language or DSL.

When to use: Simple DSLs — discount rule expressions (AMOUNT > 1000 AND CATEGORY = "ELECTRONICS"), permission expressions, SQL-like filter queries, configuration expressions. Avoid for complex languages — use ANTLR, JavaCC, or a proper parser generator instead. Interpreter doesn't scale well beyond simple grammars.

Spring Expression Language (SpEL) is the most important real-world Interpreter example in Spring Boot. #{orderService.getTotal() > 100}, @PreAuthorize("hasRole('ADMIN') and #user.id == authentication.principal.id") — all interpreted by Spring's SpEL engine.

JavaDiscount Rule Interpreter DSL + Spring SpEL Usage
// ABSTRACT EXPRESSION — base of the expression hierarchy
public interface RuleExpression {
    boolean interpret(RuleContext context);
    String  describe();
}

// TERMINAL EXPRESSIONS — leaf nodes (no children)

// Terminal: check cart total against a threshold
public class AmountGreaterThan implements RuleExpression {
    private final BigDecimal threshold;
    public AmountGreaterThan(BigDecimal t) { this.threshold = t; }

    @Override
    public boolean interpret(RuleContext ctx) {
        return ctx.getCartTotal().compareTo(threshold) > 0;
    }
    @Override
    public String describe() { return "AMOUNT > " + threshold; }
}

// Terminal: check customer tier
public class CustomerTierEquals implements RuleExpression {
    private final String tier;
    public CustomerTierEquals(String t) { this.tier = t; }

    @Override
    public boolean interpret(RuleContext ctx) {
        return tier.equals(ctx.getCustomerTier());
    }
    @Override
    public String describe() { return "TIER = " + tier; }
}

// Terminal: check category
public class CategoryContains implements RuleExpression {
    private final String category;
    @Override
    public boolean interpret(RuleContext ctx) {
        return ctx.getCategories().contains(category);
    }
    @Override public String describe() { return "CATEGORY = " + category; }
}

// NON-TERMINAL EXPRESSIONS — composites with children

// AND: both children must be true
public class AndExpression implements RuleExpression {
    private final RuleExpression left, right;
    public AndExpression(RuleExpression l, RuleExpression r) { left=l; right=r; }

    @Override
    public boolean interpret(RuleContext ctx) {
        return left.interpret(ctx) && right.interpret(ctx);
    }
    @Override public String describe() { return "(" + left.describe() + " AND " + right.describe() + ")"; }
}

// OR: either child can be true
public class OrExpression implements RuleExpression {
    private final RuleExpression left, right;
    public OrExpression(RuleExpression l, RuleExpression r) { left=l; right=r; }

    @Override
    public boolean interpret(RuleContext ctx) {
        return left.interpret(ctx) || right.interpret(ctx);
    }
    @Override public String describe() { return "(" + left.describe() + " OR " + right.describe() + ")"; }
}

// NOT
public class NotExpression implements RuleExpression {
    private final RuleExpression inner;
    @Override
    public boolean interpret(RuleContext ctx) { return !inner.interpret(ctx); }
    @Override public String describe() { return "NOT(" + inner.describe() + ")"; }
}

// BUILD AND EVALUATE rule expressions
// "(AMOUNT > 5000 AND TIER = GOLD) OR CATEGORY = electronics"
RuleExpression rule = new OrExpression(
    new AndExpression(
        new AmountGreaterThan(BigDecimal.valueOf(5000)),
        new CustomerTierEquals("GOLD")
    ),
    new CategoryContains("electronics")
);
System.out.println(rule.describe()); // prints the expression tree
System.out.println(rule.interpret(ctx)); // true/false evaluation

// SPRING SpEL — the production-grade Interpreter for Spring:
ExpressionParser parser = new SpelExpressionParser();

// 1. Simple math / conditional
Expression expr = parser.parseExpression("2 + 2 == 4 ? 'yes' : 'no'");
String result = (String) expr.getValue(); // "yes"

// 2. Bean property access
Expression orderExpr = parser.parseExpression("order.total > 1000");
StandardEvaluationContext ctx = new StandardEvaluationContext();
ctx.setVariable("order", myOrder);
boolean isHighValue = (Boolean) orderExpr.getValue(ctx);

// 3. @Value injection — Interpreter evaluates at bean initialization
@Value("#{systemProperties['user.home'] + '/logs'}")
private String logDir; // SpEL interpreted at startup

// 4. @PreAuthorize — security expression interpreter
@PreAuthorize("hasRole('ADMIN') and #userId == authentication.principal.id")
public User getUserDetails(@PathVariable UUID userId) { ... }
// SpEL evaluates the expression on every call — double dispatch under the hood

// 5. Spring Cache @Cacheable key generation
@Cacheable(value = "orders", key = "#customerId + '_' + #status")
public List findOrders(UUID customerId, OrderStatus status) { ... }
🔩 Where Interpreter Is Used in Java Frameworks
Spring SpEL
Spring Expression Language interprets #{...} expressions in @Value, @PreAuthorize, @Cacheable key, @ConditionalOnExpression. Full language with operators, method calls, conditional, regex.
Spring Security Method Security
@PreAuthorize("hasRole('ADMIN') and #id == principal.id") — SpEL interpreter evaluates security expressions. MethodSecurityExpressionHandler provides the security-specific context (roles, authentication object).
Hibernate JPQL / HQL
JPQL is a language with its own grammar. Hibernate parses and interprets: SELECT o FROM Order o WHERE o.total > :min. The interpreter translates JPQL to SQL specific to the target database dialect.
Spring Data @Query
@Query("SELECT u FROM User u WHERE u.email = :email") — Spring Data interprets JPQL/SQL strings at repository level. Native queries are passed to DB; JPQL goes through the Hibernate interpreter.
Thymeleaf Expression Engine
Thymeleaf's th:text="${order.total}", th:if="${user.isAdmin()}" uses its own expression parser and interpreter to evaluate expressions against the Spring model context.
🎯 Interview Questions — Interpreter
When would you implement an Interpreter pattern? When would you NOT?
Use Interpreter when: (1) You have a simple, stable grammar with a limited number of rules. (2) Non-developers need to configure business logic (discount rules, alert conditions, permission expressions). (3) Efficiency is not the primary concern. (4) The grammar is so simple that a full parser generator is overkill.
Do NOT use when: (1) The language is complex (use ANTLR, JavaCC, or a parser combinator library like jparsec). (2) Performance is critical (Interpreter is slow — every evaluation traverses the expression tree). (3) Grammar changes frequently (harder to extend Interpreter than a proper parser). (4) You need error reporting (Interpreter gives poor error messages).
Real-world decision: SpEL for Spring-native expressions (already built), ANTLR for anything custom with complex grammar, Interpreter pattern only for very simple, isolated DSLs with 5-10 rule types.
How does SpEL work internally? How does it implement Interpreter pattern?
SpEL parsing produces an AST (Abstract Syntax Tree) — each node is an Expression (Terminal or NonTerminal). ExpressionParser.parseExpression("2 + 2") returns a tree: OpPlus(IntLiteral(2), IntLiteral(2)).
Interpretation: expression.getValue(context) recursively evaluates each node. OpPlus.getValue(ctx) calls left.getValue(ctx) (= 2) and right.getValue(ctx) (= 2) and returns 4. This IS the Interpreter pattern — each AST node knows how to evaluate itself.
Optimization: SpEL compiles frequently-evaluated expressions to bytecode via ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, getClass().getClassLoader())). Compiled expressions bypass tree traversal, running at near-native Java speed. Critical for high-frequency expressions like cache keys.
📋
All 23 GoF Patterns — Master Reference
One-line intent, key Java example, Spring usage — all from memory
PatternCategoryOne-Line IntentKey Java ExampleSpring Usage
SingletonCreationalOne instance, global accessHikariCP, Logger@Bean default scope
Factory MethodCreationalSubclass decides which class to createPaymentProcessor selectionFactoryBean<T>, @ConditionalOn
Abstract FactoryCreationalFamily of related objectsEmail+SMS+Push per environment@Profile @Configuration
BuilderCreationalConstruct complex objects step by stepCreateOrderRequest.builder()@Builder, ResponseEntity.ok().body()
PrototypeCreationalClone existing instead of newEmailTemplate registry + clone@Scope("prototype")
AdapterStructuralMake incompatible interfaces work togetherSoapPaymentAdapterHandlerAdapter, HttpMessageConverter
DecoratorStructuralAdd behavior dynamically to same interfaceLoggingOrderService chain@Transactional, @Cacheable, AOP
FacadeStructuralSimplified interface to complex subsystemOrderFacade → 5 servicesJdbcTemplate, SLF4J, ApplicationContext
ProxyStructuralControl access to another objectSpring AOP CGLIB proxyAOP, Spring Data, Feign clients
CompositeStructuralTree structure — leaf and branch treated samePermission AND/OR treeSpring Security RoleHierarchy
BridgeStructuralDecouple abstraction from implementationNotification × Channel (no subclass explosion)JDBC DriverManager, SLF4J, DataSource
FlyweightStructuralShare intrinsic state across many objectsCategoryFlyweight poolString pool, Integer cache, Hibernate L2C
ObserverBehavioralNotify all dependents on state changeOrderPlacedEvent + 4 listeners@EventListener, @TransactionalEventListener
StrategyBehavioralInterchangeable algorithms at runtimeDiscountStrategy (seasonal/loyalty/bulk)Comparator, AuthenticationProvider, RetryPolicy
Template MethodBehavioralAlgorithm skeleton in base, steps in subclassesDataExporter — fetchData+formatJdbcTemplate, AbstractController, Spring Batch
CommandBehavioralEncapsulate request as object (undo/queue)CartCommand with undo/redo stackSpring Batch Step, Kafka messages, Runnable
Chain of Resp.BehavioralPass request along handler chainAuth→RateLimit→Authz→Logic chainSpring Security filters, HandlerInterceptor
StateBehavioralBehavior changes with internal stateOrder DRAFT→CONFIRMED→PAID→SHIPPEDResilience4j CB, Spring Statemachine
IteratorBehavioralSequential access without exposing structureDateRangeIterator, fail-fast/safeCollections, Stream API, Spring Batch ItemReader
MediatorBehavioralCentral hub for object communicationChatRoom mediating between usersDispatcherServlet, WebSocket broker
MementoBehavioralSave/restore object state without breaking encapsulationOrderDraft snapshot historySpring Batch ExecutionContext, Hibernate dirty check
VisitorBehavioralNew operation on hierarchy without modifying classesTaxVisitor + JsonExportVisitor on same elementsJackson serializers, Files.walkFileTree, JPA Criteria
InterpreterBehavioralGrammar + interpreter for simple languageDiscount rule DSL (AND/OR/NOT)Spring SpEL, @PreAuthorize, JPQL, Thymeleaf
🌱
Spring Framework → GoF Pattern Mapping
Every major Spring annotation/class mapped to the pattern(s) it implements
@Bean (default scope)
Singleton — one instance per ApplicationContext
@Bean @Scope("prototype")
Prototype — new instance per injection
@Configuration + CGLIB
Singleton — CGLIB intercepts @Bean calls, returns cached
FactoryBean<T>
Factory Method — complex bean creation delegated
@Profile @Configuration
Abstract Factory — family of beans per environment
ResponseEntity.ok().body()
Builder — step-by-step HTTP response construction
@Transactional
Proxy + Decorator — CGLIB wraps method in TX logic
@Cacheable / @CacheEvict
Proxy + Decorator — AOP proxy wraps with cache check
@Async
Proxy + Decorator — proxy submits to thread pool
JdbcTemplate.query()
Template Method + Facade — fixed lifecycle, you provide SQL+mapper
HandlerAdapter
Adapter — adapts @Controller to DispatcherServlet
HttpMessageConverter
Adapter + Strategy — adapts Java ↔ HTTP bodies per content type
DispatcherServlet
Mediator — central hub for HandlerMappings, Adapters, ViewResolvers
ApplicationEventPublisher
Observer — publisher → all @EventListener subscribers
@TransactionalEventListener
Observer — fires at specific TX phase (AFTER_COMMIT etc.)
AuthenticationProvider
Strategy — ProviderManager tries each until one succeeds
Spring Security FilterChain
Chain of Responsibility — each filter short-circuits or passes on
HandlerInterceptor
Chain of Responsibility — preHandle() returns false to stop
RoleHierarchyImpl
Composite — admin includes manager includes user roles
Spring Security CompositeFilter
Composite — one filter interface wrapping N filters
SLF4J Logger
Facade + Bridge — SLF4J API bridges to Logback/Log4j2 implementation
DataSource (HikariCP, DBCP)
Bridge — DataSource abstraction over specific pool impl
JDBC DriverManager
Bridge — java.sql.Connection abstraction over DB drivers
Spring Batch Step
Command — each Step.execute() is a discrete command
Spring Batch ExecutionContext
Memento — checkpoint saved to DB for restart capability
Spring Batch ItemReader
Iterator + Template Method — read() returns next or null
Spring Data JPA Repository
Proxy — JDK proxy implementing your Repository interface
Hibernate lazy loading
Proxy (Virtual) — PersistentBag defers SQL until first access
Hibernate dirty checking
Memento — snapshot taken on load, compared on flush
Spring SpEL
Interpreter — parses and evaluates #{...} expressions
@PreAuthorize("...")
Interpreter + Proxy — SpEL expression evaluated via AOP proxy
Resilience4j @CircuitBreaker
State + Proxy + Decorator — three-state machine via AOP proxy
String pool / Integer cache
Flyweight — shared immutable objects per unique value
Feign / OpenFeign
Proxy — JDK proxy translating interface calls to HTTP requests
⚠️
Anti-Patterns to Avoid
What bad design looks like — and how design patterns fix it
God Class / God Object
One class with hundreds of methods doing everything — order processing, payment, inventory, notifications, reporting. Impossible to test, extend, or understand.
Fix: Facade (split into focused services) + Observer (decouple side effects)
Anemic Domain Model
Order class with only getters/setters and no behavior. All business logic in a bloated OrderService. Violates OOP — data and behavior should be co-located.
Fix: Rich domain model → Order.confirm(), Order.cancel() contain business logic + State pattern
Service Locator
ServiceLocator.get("OrderService") hidden inside classes. Hidden dependencies. Hard to test — can't inject mocks. Anti-pattern alternative to DI.
Fix: Dependency Injection (constructor injection). Spring @Autowired replaces Service Locator.
Big Switch / If-Else Chain
if payment == "UPI" ... else if payment == "CARD" ... else if payment == "COD" ... Adding new type = modify existing code. Violates OCP.
Fix: Strategy or Factory Method — each case = one class implementing interface
Singleton Abuse
Using Singleton for everything including stateful beans, making unit tests impossible and creating hidden global state. "Let's make it Singleton so it's fast."
Fix: Use Spring DI for proper lifecycle management. Stateful beans use prototype scope.
Shotgun Surgery
One change requires modifications in many unrelated classes. Adding a new Order status means changing the DB schema, service layer, controller, 3 DTOs, 2 validators, 5 tests...
Fix: State pattern + Observer — behavior contained in state classes, side effects via events
Poltergeist
Small classes that just pass control to another class and then disappear. Pure boilerplate with no real responsibility — they add complexity for zero value.
Fix: Merge into the class they delegate to, or use Facade to wrap properly
Magic Numbers / Strings
Scattered if (status == 3), if (type.equals("GOLD")) throughout codebase. No context. Changed in one place, missed in another.
Fix: Enums + Strategy/State — enum captures intent; patterns eliminate conditional branching
🎯
Architect-Level Interview Questions
Cross-pattern, system-design, and senior-level questions
🏗️ Senior / Architect Interview Questions
Name 5 design patterns used in Spring Framework and explain how each is implemented.
  1. Singleton (@Bean): Spring's CGLIB proxy intercepts @Bean method calls and returns the cached instance from DefaultSingletonBeanRegistry. Every @Bean is singleton by default.
  2. Proxy + Decorator (@Transactional): Spring generates a CGLIB subclass that wraps your method in TX begin/commit/rollback logic. The real method is called via super. Self-invocation bypasses the proxy.
  3. Template Method (JdbcTemplate): query() defines the fixed algorithm (connect, prepare, execute, translate errors, close). You provide the variable steps (SQL, RowMapper). Framework controls the lifecycle.
  4. Observer (@EventListener): ApplicationEventPublisher.publishEvent() notifies all @EventListener methods. @TransactionalEventListener fires at specific TX phase. Decouples event producers from consumers.
  5. Chain of Responsibility (Security FilterChain): FilterChainProxy chains 15+ security filters. Each calls chain.doFilter() to continue or returns early (sets 401/403) to short-circuit. Order is critical.
How do you design an order processing system using 5 microservice patterns?
  • Observer / Event-Driven: OrderService publishes OrderPlacedEvent. @TransactionalEventListener(AFTER_COMMIT) publishes to Kafka. Downstream services (inventory, shipping, notifications) are Kafka consumers — independent, can be offline.
  • Saga Pattern (CoR-inspired): Order placement saga: reserve inventory → charge payment → schedule shipping. Each step is a Command. On payment failure: compensating transaction releases inventory. Choreography via Kafka events.
  • Command / Outbox: ORDER_PLACED event stored atomically in outbox table within the same transaction. Debezium CDC reads outbox and publishes to Kafka — guarantees exactly-once publish semantics.
  • State Machine: OrderContext uses State pattern: DRAFT→CONFIRMED→PAID→SHIPPED→DELIVERED with Spring Statemachine. State persisted to DB. Invalid transitions throw exceptions — enforced at the state class level.
  • Circuit Breaker (State): Each external service call wrapped in Resilience4j @CircuitBreaker. CLOSED→OPEN→HALF_OPEN state machine prevents cascading failures when payment gateway is down.
Which patterns work best together? Give 3 combinations you've used in production.
  • Factory Method + Strategy: Factory selects the right PaymentStrategy at runtime. Each strategy is independently testable. Adding a new payment method = new @Service class implementing both interfaces. Zero changes to factory or caller.
  • Observer + Command: Observer (Spring Events) triggers a Command (Kafka message). @TransactionalEventListener(AFTER_COMMIT) publishes serialized Command to Kafka topic. Consumer deserializes and executes the command asynchronously. Decouples trigger from execution.
  • Decorator + Template Method: JdbcTemplate (Template Method) wraps JDBC. LoggingOrderService (Decorator) wraps business services. @Transactional (Decorator/Proxy) wraps the service method. Three decoration layers — each adds one concern without touching the core.
What is the Anemic Domain Model? How do you fix it with design patterns?
Anemic Domain Model: Order class contains only getters/setters. All business logic lives in OrderService: orderService.confirmOrder(order), orderService.cancelOrder(order, reason). This violates OOP — data and behavior should be co-located. The "domain model" is just a data container.
Problems: Business rules scattered across service methods. No enforcement of invariants. Easy to put an Order in invalid state (call cancelOrder after deliverOrder). Hard to find business logic.
Fix with State pattern: order.confirm(), order.cancel(reason), order.ship(trackingCode) — methods live ON the Order. State pattern ensures invalid transitions throw InvalidOrderStateException at the domain level. Business rules enforced by the entity itself, not by the service. The service becomes a coordinator (calls domain methods and persists), not the business logic holder.
How do you decide which pattern to apply? Walk through your decision process.
Start with the problem category:
  • Creation problem? → Creational. One type? Factory Method. Family? Abstract Factory. Many optional params? Builder. Expensive to create? Prototype. Need exactly one? Singleton.
  • Structure problem? → Structural. Incompatible interfaces? Adapter. Add behavior same interface? Decorator. Simplify complex subsystem? Facade. Control access? Proxy. Tree hierarchy? Composite.
  • Communication problem? → Behavioral. Who calls whom? Notify many? Observer. Pick algorithm at runtime? Strategy. Algorithm skeleton? Template Method. Encapsulate request? Command. Handler chain? CoR. State-dependent behavior? State.
Then validate: Does the solution actually simplify the code or add unnecessary complexity? If you're adding a pattern to a 20-line class that works fine — skip it. Patterns solve recurring problems; don't force them where there's no problem.