Bird
Raised Fist0

Strategy Pattern - Pluggable Algorithms and Runtime Behavior Swap

LLDeasyInterview freq: high
Choose your preparation mode4 modes available
๐ŸŽฏ
Strategy Pattern - Pluggable Algorithms and Runtime Behavior Swap
40 min ยท medium
medium

Design a payment processing system that supports multiple payment algorithms (e.g., CreditCard, PayPal, Cryptocurrency). The system must allow swapping payment strategies at runtime without modifying existing code. It should handle 5 user levels with different pricing structures and 4 payment gateways. The system must be thread-safe to handle concurrent payment requests.

  • There are 5 user levels, each with distinct pricing rules.
  • 4 payment gateways must be supported, each with different transaction protocols.
  • Payment algorithms must be pluggable and swappable at runtime.
  • Concurrency: multiple threads can process payments simultaneously.
  • Pricing strategy depends on user level and payment method.
  • The system must be extensible without modifying existing classes (Open/Closed Principle).
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define the PaymentStrategy interface
Design a Java interface representing the payment algorithm abstraction that all concrete payment strategies will implement.
๐Ÿ’ก Hint
Use a Java interface to define the contract for payment processing behavior without holding state.
Use a Java interface PaymentStrategy with method boolean processPayment(User user, double amount) - interface chosen because it defines behavior without instance state, enabling pluggability.
Mid: Recognizes Strategy pattern with interface abstraction. Senior: Explains interface cannot hold instance fields in Java, enabling multiple implementations without shared state.
โš ๏ธCommon Wrong Answer
Using an abstract class with state for PaymentStrategy leads to unnecessary coupling and reduces flexibility to swap strategies at runtime.
Step 2
Implement concrete payment strategies
Implement at least two concrete payment strategy classes that implement PaymentStrategy, each with their own payment logic.
๐Ÿ’ก Hint
Each concrete class implements processPayment() with its own algorithm; no shared mutable state in interface.
Concrete classes CreditCardPaymentStrategy and PayPalPaymentStrategy implement PaymentStrategy interface, encapsulating distinct payment algorithms without shared mutable state.
Mid: Identifies concrete strategy implementations. Senior: Explains how polymorphism enables runtime swapping and how lack of shared mutable state avoids concurrency issues.
โš ๏ธCommon Wrong Answer
Implementing payment logic in a single class with if-else for payment type violates Open/Closed Principle and makes runtime swapping impossible.
Step 3
Design the PaymentContext class for runtime strategy swapping
Create a context class that holds a reference to a PaymentStrategy and delegates payment processing to it. Allow changing the strategy at runtime.
๐Ÿ’ก Hint
Use a private volatile PaymentStrategy field and synchronized setter to ensure thread-safe strategy swapping.
PaymentContext holds private volatile PaymentStrategy strategy; public synchronized void setStrategy(PaymentStrategy s) ensures atomic strategy swap visible to all threads.
Mid: Understands context delegates to strategy. Senior: Explains volatile ensures visibility of strategy changes across threads; synchronized setter prevents race conditions during swap.
โš ๏ธCommon Wrong Answer
Not using volatile or synchronization for strategy field leads to visibility issues and race conditions where threads see stale or inconsistent strategy references.
Step 4
Incorporate user levels and pricing strategy
Design a pricing strategy that varies based on user level and payment method, and integrate it with the payment processing system.
๐Ÿ’ก Hint
Use an enum for UserLevel and a separate PricingStrategy interface with concrete implementations per user level; inject pricing strategy into PaymentContext.
UserLevel enum defines fixed levels; PricingStrategy interface with concrete classes per level enables Open/Closed Principle; PaymentContext composes PricingStrategy (composition) to calculate price.
Mid: Uses enum for user levels and separate pricing strategy. Senior: Explains enum prevents runtime typos; composition lifecycle means PricingStrategy exists only within PaymentContext.
โš ๏ธCommon Wrong Answer
Using String for user levels causes runtime bugs due to typos; embedding pricing logic inside payment strategies violates Single Responsibility Principle.
Step 5
Ensure thread-safe payment processing
Make payment processing thread-safe to handle concurrent payment requests without corrupting shared state.
๐Ÿ’ก Hint
Use synchronized boolean processPayment(...) in PaymentContext to atomically delegate to current strategy; avoid mutable shared state in strategies.
PaymentContext.processPayment(User user, double amount) is synchronized to atomically invoke strategy.processPayment; strategies are stateless or use thread-safe constructs.
Mid: Recognizes need for thread safety in payment processing. Senior: Explains synchronized method prevents interleaving where multiple threads corrupt shared state or see inconsistent strategy.
โš ๏ธCommon Wrong Answer
Not synchronizing processPayment leads to race conditions where multiple threads interfere, causing inconsistent payment states or double charges.
๐Ÿ”Œ
API Design Challenge
Design a Java-based payment processing API that supports multiple payment algorithms and user-level pricing, allowing runtime swapping of payment strategies while ensuring thread safety.
  • Define a PaymentStrategy interface with a processPayment method to abstract payment algorithms.
  • Use a volatile PaymentStrategy field in PaymentContext with synchronized setter to ensure thread-safe swapping.
  • Implement a PricingStrategy interface with concrete classes per user level to separate pricing logic.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • PaymentStrategy interface without instance state
  • Concrete payment strategy classes implementing PaymentStrategy
  • PaymentContext with volatile PaymentStrategy field and synchronized setter
  • UserLevel enum for user levels
  • Synchronized processPayment method in PaymentContext
Good to Have - 30%
  • PricingStrategy interface with concrete implementations per user level
  • Composition relationship between PaymentContext and PricingStrategy
  • Stateless or thread-safe payment strategies
  • Use of volatile for visibility guarantees
  • Clear separation of concerns between payment and pricing logic
Bonus - 10%
  • Demonstration of runtime swapping of strategies with example code
  • Explanation of concurrency scenarios with thread interleaving
  • Use of Java concurrency utilities if applicable
Using String for user levels causes runtime bugs due to typos and makes code fragile; prefer enum to enforce compile-time checks.
Embedding pricing logic inside payment strategies violates Single Responsibility Principle, making maintenance and extension harder.
Not synchronizing access to the strategy field leads to race conditions and visibility issues, causing inconsistent payment processing.
Implementing payment logic in a single class with if-else for payment type violates Open/Closed Principle and prevents runtime swapping.
Using an abstract class with state for PaymentStrategy leads to unnecessary coupling and reduces flexibility to swap strategies at runtime.
โœ…
Model Answer
โ–ผ Reveal

This design demonstrates the Strategy Pattern in Java to enable pluggable payment algorithms and runtime swapping of payment strategies. It supports multiple user levels with distinct pricing strategies and ensures thread-safe payment processing in a concurrent environment.

  • Use of Java interface for PaymentStrategy to define behavior without instance state, enabling pluggability and concurrency safety.
  • Concrete payment strategies implement PaymentStrategy with distinct algorithms, avoiding shared mutable state to prevent concurrency bugs.
  • PaymentContext uses a private volatile PaymentStrategy field to ensure visibility of strategy changes across threads.
  • Synchronized setter method in PaymentContext to atomically swap strategies and prevent race conditions.
  • Synchronized processPayment method in PaymentContext to ensure atomic delegation to the current strategy during concurrent payment requests.
  • UserLevel enum to define fixed user levels, preventing runtime typos and enabling exhaustive switch statements.
  • PricingStrategy interface with concrete classes per user level to separate pricing logic and adhere to Open/Closed and Single Responsibility Principles.
  • Composition of PricingStrategy within PaymentContext to bind pricing lifecycle to payment processing context.
  • PaymentStrategy interface defines processPayment(User user, double amount) without instance state, enabling pluggable algorithms.
  • Concrete payment strategies implement PaymentStrategy with distinct algorithms, avoiding shared mutable state.
  • PaymentContext holds volatile PaymentStrategy strategy field; synchronized setter ensures thread-safe runtime swapping.
  • UserLevel enum defines fixed user levels, preventing runtime typos and enabling exhaustive switch statements.
  • PricingStrategy interface with concrete implementations per user level enforces Open/Closed Principle and Single Responsibility Principle.
  • PaymentContext composes PricingStrategy, lifecycle bound to context (composition).
  • PaymentContext.processPayment is synchronized to ensure atomic delegation to current strategy, preventing race conditions.
  • Strategies are stateless or use thread-safe constructs to avoid concurrency bugs.
  • Use of volatile ensures visibility of strategy changes across threads, preventing stale reads.
  • Avoid embedding pricing logic inside payment strategies to maintain separation of concerns.

Practice

(1/5)
1. In a vehicle rental system, should Vehicle be designed as an abstract class or an interface if it needs to hold common state like licensePlate and rentalStatus? Why?
easy
A. Abstract class, because it forces all subclasses to implement all methods explicitly.
B. Interface, because it allows multiple inheritance and enforces method signatures without state.
C. Interface, because Java interfaces can now have default methods and fields for state.
D. Abstract class, because it can hold shared state and provide default behavior for subclasses.

Solution

  1. Step 1: Analyze state requirements

    Vehicle needs to hold common mutable state like licensePlate and rentalStatus, which interfaces cannot hold as fields.
  2. Step 2: Choose correct OOP construct

    Abstract classes can have instance variables and default method implementations, making them suitable for shared state. Interfaces cannot hold instance state, only constants.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Shared mutable state -> abstract class, not interface [check]
Hint: Shared state needs abstract class, interfaces only define behavior [check]
Common Mistakes:
2. In designing the Parking Lot system, should Vehicle be an abstract class or an interface, and why?
easy
A. Vehicle should be an abstract class because it holds common state like license plate and size.
B. Vehicle should be an interface to allow multiple inheritance of vehicle types.
C. Vehicle should be a concrete class to allow direct instantiation of generic vehicles.
D. Vehicle should be an interface because it only defines behavior without state.

Solution

  1. Step 1: Identify if Vehicle has shared state

    Vehicle has common fields like license plate, size, and type that need to be stored and reused.
  2. Step 2: Abstract class vs interface for shared state

    Interfaces cannot hold state in Java (prior to default methods), so abstract class is needed to encapsulate shared state and partial implementation.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Shared state in Vehicle -> abstract class needed [check]
Hint: Shared state? Use abstract class, not interface [check]
Common Mistakes:
3. In designing the traffic light system, should Vehicle be an abstract class or an interface, considering vehicles have state like speed and direction? Why?
easy
A. Vehicle should be an interface because it only defines behavior without state
B. Vehicle should be an interface with default methods to hold state
C. Vehicle should be an abstract class to encapsulate shared state and behavior
D. Vehicle should be a final class to prevent subclassing and ensure immutability

Solution

  1. Step 1: Analyze state vs behavior requirements

    Vehicles have shared state (speed, direction) and behavior; interfaces cannot hold instance state, so interface alone is insufficient.
  2. Step 2: Abstract class encapsulates shared state and common methods

    Abstract class allows shared fields and partial implementation, enabling subclasses to extend and customize behavior.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Stateful base class -> abstract class, not interface [check]
Hint: Stateful base class -> abstract class, stateless -> interface [check]
Common Mistakes:
4. In a vending machine design, a State interface defines methods like selectProduct(), insertMoney(), and dispense(). Concrete classes like IdleState, HasMoneyState, and SoldState implement this interface. Which design pattern is applied here and why is it the correct choice?
easy
A. Observer pattern, because states notify the vending machine when transitions occur.
B. Strategy pattern, because it allows changing algorithms at runtime for product selection.
C. Decorator pattern, because it adds responsibilities to states dynamically without altering their interface.
D. State pattern, because it encapsulates state-specific behavior and transitions within state classes.

Solution

  1. Step 1: Identify the role of State interface and concrete states

    The State interface defines behavior that varies depending on the vending machine's current state. Concrete classes implement these behaviors and handle transitions.
  2. Step 2: Match pattern intent to scenario

    The State pattern encapsulates state-specific behavior and transitions, allowing the vending machine to change behavior dynamically without complex conditionals.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    State pattern encapsulates state behavior and transitions [check]
Hint: State pattern encapsulates behavior per state [check]
Common Mistakes:
5. In a multi-threaded chain of responsibility, if the method handleRequest() is not synchronized, which exact sequence of events can cause a request to be handled twice by two handlers?
medium
A. Thread 1 checks canHandle() -> true, context switch; Thread 2 checks canHandle() -> true; both handle the request concurrently.
B. Thread 1 finishes handling, Thread 2 starts after; no overlap occurs.
C. Thread 1 and Thread 2 both call handleRequest() simultaneously and both skip to next handler.
D. Thread 1 locks the handler, Thread 2 waits until unlock; no double handling.

Solution

  1. Step 1: Identify race condition

    Without synchronization, two threads can both see canHandle() as true before either marks the request handled.
  2. Step 2: Trace thread interleaving

    Thread 1 checks canHandle() -> true, context switch before marking handled; Thread 2 also checks canHandle() -> true and proceeds; both handle the same request.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Check-then-act gap causes double handling [check]
Hint: Check-then-act gap causes double handling [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