Bird
Raised Fist0

Command Pattern - Encapsulating Operations for Undo, Redo, Queue

LLDeasyInterview freq: common
Choose your preparation mode4 modes available
๐ŸŽฏ
Command Pattern - Encapsulating Operations for Undo, Redo, Queue
40 min ยท medium
medium

Design a system that uses the Command Pattern to encapsulate operations such as undo, redo, and queuing commands. The system must support concurrent command execution requests, maintain a history for undo/redo, and allow extensibility for new command types without modifying existing code.

  • Commands must be encapsulated as objects implementing a common interface or abstract class.
  • Undo and redo operations require maintaining a command history stack.
  • Multiple threads may submit commands concurrently; thread safety is required.
  • The system should support queuing commands for sequential execution.
  • Extensibility must follow the Open/Closed Principle to add new commands without modifying existing classes.
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define the Command Interface or Abstract Class
Design the base abstraction for commands that encapsulate an operation with execute(), undo(), and redo() methods.
๐Ÿ’ก Hint
Use an abstract class if you need to hold instance state or provide default implementations; interfaces cannot hold instance fields in Java.
abstract class Command with abstract methods execute(), undo(), redo() - abstract class chosen because it can hold shared instance state and provide default behavior, which interfaces cannot in Java.
Mid: Names Command Pattern and abstract class usage. Senior: Explains why abstract class is needed for shared state or default methods, referencing Java's inability to hold fields in interfaces before Java 8 default methods.
โš ๏ธCommon Wrong Answer
Using interface Command with default methods but needing instance fields leads to inability to store command state, causing design inflexibility.
Step 2
Implement Command History for Undo/Redo
Design a thread-safe mechanism to store executed commands for undo and redo operations.
๐Ÿ’ก Hint
Use two stacks (undoStack and redoStack) with synchronized methods or concurrent collections; volatile alone is insufficient for compound operations.
Use synchronized methods pushCommand(Command c), undo(), redo() to atomically update undoStack and redoStack - volatile alone cannot guarantee atomicity of compound stack operations in Java.
Mid: Describes Command History with stacks. Senior: Traces race condition where Thread 1 reads undoStack empty, Thread 2 pushes command, Thread 1 proceeds incorrectly without synchronization.
โš ๏ธCommon Wrong Answer
Using volatile stacks without synchronization allows race conditions where undoStack and redoStack states become inconsistent, causing incorrect undo/redo behavior.
Step 3
Design Command Queue for Sequential Execution
Implement a thread-safe queue to hold commands submitted concurrently and execute them sequentially.
๐Ÿ’ก Hint
Use a BlockingQueue<Command> with a dedicated worker thread consuming commands; synchronized blocks alone are error-prone for producer-consumer scenarios.
Use java.util.concurrent.LinkedBlockingQueue<Command> for thread-safe command queuing and a single worker thread polling commands to execute sequentially - leverages Java concurrency utilities for correctness.
Mid: Names producer-consumer pattern with queue. Senior: Explains how LinkedBlockingQueue prevents lost commands and ensures thread-safe handoff between producer threads and consumer thread.
โš ๏ธCommon Wrong Answer
Using a non-thread-safe queue with synchronized blocks risks missed signals or deadlocks, causing commands to be lost or executed out of order.
Step 4
Ensure Extensibility for New Command Types
Design the system to allow adding new command types without modifying existing code, adhering to the Open/Closed Principle.
๐Ÿ’ก Hint
Use inheritance from abstract Command class and polymorphism; avoid switch-case on command types or instanceof checks.
Use inheritance and polymorphism with abstract class Command and concrete subclasses overriding execute(), undo(), redo() - avoids type checks and enables Open/Closed Principle compliance in Java.
Mid: Identifies Open/Closed Principle and polymorphism. Senior: Explains how adding new Command subclasses requires no changes to existing code, preventing regression bugs.
โš ๏ธCommon Wrong Answer
Using switch-case on command type enums in a central executor violates Open/Closed Principle and requires modifying existing code for new commands, increasing bug risk.
Step 5
Handle Concurrency in Command Execution and History Updates
Design concurrency control to prevent race conditions during command execution and history updates.
๐Ÿ’ก Hint
Synchronize execute(), undo(), redo() methods or use ReentrantLock to ensure atomicity; volatile alone cannot protect compound state changes.
Declare execute(), undo(), redo() as public synchronized methods in CommandInvoker to atomically perform command execution and update history stacks - prevents interleaving that corrupts history state.
Mid: Names synchronized keyword for mutual exclusion. Senior: Traces thread interleaving where Thread 1 executes command and updates undoStack, Thread 2 concurrently modifies undoStack causing inconsistent history.
โš ๏ธCommon Wrong Answer
Relying on volatile flags without synchronized blocks allows interleaved updates to history stacks, causing lost undo/redo commands and inconsistent state.
๐Ÿ”Œ
API Design Challenge
Design an API that allows clients to submit commands concurrently, supports undo and redo operations, and enables sequential execution of commands with thread safety.
  • Use an abstract Command class with execute(), undo(), and redo() methods to encapsulate operations.
  • Implement thread-safe command history using synchronized methods or locks to maintain undo and redo stacks.
  • Use a LinkedBlockingQueue with a dedicated worker thread to process commands sequentially and safely.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • abstract class Command with execute(), undo(), redo() methods
  • Thread-safe undo/redo history with synchronized methods
  • Thread-safe command queue using LinkedBlockingQueue
  • Polymorphic Command subclasses for extensibility
  • Synchronized command execution and history updates
Good to Have - 30%
  • Dedicated worker thread for sequential command execution
  • Avoidance of switch-case on command types
  • Clear ownership (composition) of command history stacks
  • Use of Java concurrency utilities for queue management
Bonus - 10%
  • Use of ReentrantLock instead of synchronized for finer control
  • Support for command batching or composite commands
Using interface Command with default methods but needing instance fields leads to inability to store command state, causing design inflexibility.
Using volatile stacks without synchronization allows race conditions where undoStack and redoStack states become inconsistent, causing incorrect undo/redo behavior.
Using a non-thread-safe queue with synchronized blocks risks missed signals or deadlocks, causing commands to be lost or executed out of order.
Using switch-case on command type enums in a central executor violates Open/Closed Principle and requires modifying existing code for new commands, increasing bug risk.
Relying on volatile flags without synchronized blocks allows interleaved updates to history stacks, causing lost undo/redo commands and inconsistent state.
โœ…
Model Answer
โ–ผ Reveal

    • abstract class Command with abstract execute(), undo(), redo() methods to encapsulate operations and hold instance state.
    • CommandInvoker class with synchronized methods to execute commands and update undo/redo stacks atomically.
    • Use two stacks (undoStack and redoStack) to maintain command history for undo and redo operations.
    • Use java.util.concurrent.LinkedBlockingQueue<Command> for thread-safe command queuing and sequential execution by a dedicated worker thread.
    • Polymorphic Command subclasses to add new commands without modifying existing code, enforcing Open/Closed Principle.
    • Synchronized execute(), undo(), redo() methods in CommandInvoker to prevent race conditions during command execution and history updates.
    • Avoid switch-case or instanceof checks on command types to maintain extensibility and type safety.
    • Volatile alone is insufficient for compound state changes; synchronized or locks are required for atomicity.
    • CommandInvoker owns the lifecycle of undoStack and redoStack (composition), ensuring history is consistent and encapsulated.
    • Worker thread for command queue runs independently but uses composition to access CommandInvoker for execution.

    Practice

    (1/5)
    1. In a Java codebase, a factory class provides methods to create related objects like Button and Checkbox families without specifying their concrete classes. Which design pattern is applied here and why is it the correct choice?
    easy
    A. Singleton pattern, because it ensures only one factory instance exists.
    B. Builder pattern, because it constructs complex objects step-by-step.
    C. Abstract Factory pattern, because it creates families of related objects without specifying their concrete classes.
    D. Prototype pattern, because it clones existing objects to create new ones.

    Solution

    1. Step 1: Identify the role of the factory class

      The factory creates multiple related objects (Button, Checkbox) without exposing their concrete classes, indicating a family of products.
    2. Step 2: Match pattern intent

      Abstract Factory pattern is designed to create families of related objects without specifying their concrete classes, unlike Builder (stepwise construction), Singleton (single instance), or Prototype (cloning).
    3. Final Answer:

      Option C -> Option C
    4. Quick Check:

      Abstract Factory creates related object families without concrete classes [check]
    Hint: Abstract Factory creates related families, not single objects [check]
    Common Mistakes:
    2. In the hotel system, should Vehicle be designed as an abstract class or an interface, considering that different vehicle types have state like license plate and behavior like parking? Why?
    easy
    A. Interface, because it allows multiple inheritance and all vehicles share behavior only.
    B. Abstract class, because vehicles share common state and behavior that can be reused.
    C. Interface, because state should be stored externally to keep Vehicle stateless.
    D. Abstract class, because interfaces cannot define any methods with implementation.

    Solution

    1. Step 1: Analyze Vehicle's state and behavior

      Vehicle has common state (license plate) and behavior (park, unpark) shared by all types.
    2. Step 2: Abstract class vs interface for state and behavior

      Abstract classes can hold state and provide default behavior; interfaces cannot hold instance state (Java 8+ default methods don't hold state). Thus, abstract class fits better.
    3. Final Answer:

      Option B -> Option B
    4. Quick Check:

      Shared state + behavior -> abstract class [check]
    Hint: Stateful base class -> abstract class, not interface [check]
    Common Mistakes:
    3. In the Restaurant Management System, the OrderManager class has a private constructor and a static synchronized getInstance() method. Which design pattern is applied here and why is it the correct choice?
    easy
    A. Factory pattern, because it controls object creation centrally.
    B. Builder pattern, to incrementally construct complex orders.
    C. Singleton pattern, to ensure only one instance manages all orders globally.
    D. Prototype pattern, to clone existing orders efficiently.

    Solution

    1. Step 1: Identify the pattern from constructor and method

      The private constructor prevents external instantiation, and the static synchronized getInstance() method ensures a single shared instance, which matches the Singleton pattern.
    2. Step 2: Why Singleton fits OrderManager

      OrderManager must coordinate all orders centrally to avoid conflicting states; multiple instances would cause inconsistent order tracking.
    3. Final Answer:

      Option C -> Option C
    4. Quick Check:

      Private constructor + synchronized static getInstance() -> Singleton [check]
    Hint: Private constructor + static getInstance() -> Singleton [check]
    Common Mistakes:
    4. In the Meeting Scheduler, the RoomBookingManager class has a bookRoom() method that checks if a room is available and then marks it booked. Without synchronizing bookRoom(), which exact sequence of events between Thread 1 and Thread 2 causes a double-booking race condition?
    medium
    A. Thread 1 reads availability as false; Thread 2 reads availability as true; both proceed to book.
    B. Thread 1 and Thread 2 both wait on a lock, causing deadlock and no booking.
    C. Thread 1 books room; Thread 2 reads availability after booking and cancels its booking.
    D. Thread 1 checks isAvailable() -> true, context switch before booking; Thread 2 checks isAvailable() -> true, both book simultaneously.

    Solution

    1. Step 1: Identify check-then-act gap

      Without synchronization, Thread 1 reads availability as true but is preempted before booking; Thread 2 also reads true and proceeds.
    2. Step 2: Both threads book simultaneously causing double-booking

      Because the availability check and booking are not atomic, both threads can book the same room concurrently.
    3. Final Answer:

      Option D -> Option D
    4. Quick Check:

      Unsynchronized check-then-act -> double-booking race [check]
    Hint: Check-then-act without sync -> double-booking race [check]
    Common Mistakes:
    5. The current Order Management System uses a FeeStrategy interface for pricing. To add a WeekendFeeStrategy that applies extra fees on weekends, which minimal change respects SOLID principles?
    medium
    A. Create a WeekendFeeDecorator that wraps existing FeeStrategy implementations and adds weekend fees.
    B. Add WeekendFeeStrategy as a new class implementing FeeStrategy and configure Order to use it without modifying existing classes.
    C. Modify the existing FeeStrategy implementation to check for weekends and apply extra fees.
    D. Add a boolean weekend flag to Order and modify FeeStrategy implementations to check it.

    Solution

    1. Step 1: Identify SOLID principle involved

      Open-Closed Principle (OCP) requires adding new behavior without modifying existing code.
    2. Step 2: Evaluate options

      Add a boolean weekend flag to Order and modify FeeStrategy implementations to check it. modifies existing classes violating OCP; Modify the existing FeeStrategy implementation to check for weekends and apply extra fees. also modifies existing code; Add WeekendFeeStrategy as a new class implementing FeeStrategy and configure Order to use it without modifying existing classes. adds new class but requires Order modification; Create a WeekendFeeDecorator that wraps existing FeeStrategy implementations and adds weekend fees. adds decorator without modifying existing classes, respecting OCP and DIP.
    3. Final Answer:

      Option D -> Option D
    4. Quick Check:

      Decorator adds behavior without modifying existing classes [check]
    Hint: Decorator adds features without changing existing code [check]
    Common Mistakes:
    Recommended

    Ready to prove it?

    Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

    10 questions~5 mineasy hard