Posted on: June 24, 2025 Posted by: rahulgite Comments: 0

โœ… Scenario 1: Payment Gateway Integration using Factory Pattern

๐Ÿงฉ Problem Statement:
An application needs to support various payment modes such as Credit Card, PayPal, UPI, and Net Banking. Instantiating all services eagerly is inefficient in terms of memory and performance. The application should only instantiate the required payment service based on the user’s choice at runtime.

๐ŸŽฏ Objective:

  • Create services dynamically
  • Follow Open/Closed Principle
  • Maintain clean separation of concerns

๐Ÿ› ๏ธ Solution: Factory Pattern
The Factory Pattern is ideal when the creation of objects depends on certain conditions and you want to abstract the instantiation logic. It centralizes object creation logic and delegates it to a “factory” class.

๐Ÿงฑ Implementation Steps:

  1. Define an Interface:
    • Create a PaymentService interface with a method like processPayment().
  2. Concrete Implementations:
    • Implement PaymentService in classes like CreditCardPaymentService, PayPalPaymentService, UPIPaymentService, and NetBankingPaymentService.
  3. Create a Factory Class:
    • Implement a PaymentServiceFactory class with a static method like getPaymentService(String type).
    • Use switch or if-else statements to return the appropriate service instance.
  4. Client Interaction:
    • The client layer (frontend or controller) calls the factory and invokes processPayment() on the returned object.

โœ… Benefits:

  • Decoupling: Keeps object creation separate from business logic.
  • Extensibility: Adding new payment types requires minimal changes.
  • SRP Compliance: Each class has a single responsibility.
  • Improved Maintainability: Easy to manage and test.

๐Ÿ“Œ Interview Follow-Up Questions:

  • How is Factory different from Abstract Factory?
  • When to prefer Strategy Pattern over Factory?

โœ… Scenario 2: Immutable Order Objects with Optional Fields using Builder Pattern

๐Ÿงฉ Problem Statement:
In many enterprise applications, especially in domain-driven design, we need immutable objects like Order, which may have many optional fields. Traditional constructors become complex and hard to maintain.

๐ŸŽฏ Objective:

  • Create immutable objects with optional fields
  • Avoid telescoping constructors
  • Maintain object readability and clarity

๐Ÿ› ๏ธ Solution: Builder Pattern
The Builder Pattern solves this problem by separating the construction of a complex object from its representation.

๐Ÿงฑ Manual Implementation:

  1. Immutable Order Class:
    • Make all fields final
    • Private constructor accepting a Builder object
    • Only getters, no setters
  2. Static Inner Builder Class:
    • Contains the same fields (non-final)
    • Setter-like methods (withId, withCustomerName, etc.) return Builder for chaining
    • build() method returns a new immutable Order object
  3. Client Usage:
Order order = new Order.Builder()
                 .withId(123)
                 .withCustomerName("John Doe")
                 .build();

โœ… Benefits:

  • Immutability: No state changes after creation
  • Readability: Code is expressive and easy to follow
  • Flexibility: Only necessary fields need to be set
  • No Constructor Overload: Eliminates multiple constructors

๐Ÿ”ง Alternative:
Use Lombok’s @Builder annotation:

  • Reduces boilerplate code
  • Automatically generates builder class
UserDTO.builder().username("user1").city("New York").build();

๐Ÿ“Œ Interview Follow-Up Questions:

  • How does Lombok internally implement Builder?
  • Can Builder ensure validation or default values?

โœ… Scenario 3: API Call Optimization using Proxy Pattern with Caching

๐Ÿงฉ Problem Statement:
An app interacts with a third-party payment gateway that charges per API call. Users often check transaction status repeatedly, leading to redundant and expensive calls.

๐ŸŽฏ Objective:

  • Minimize third-party API calls
  • Avoid repeated status checks
  • Improve response time and cost efficiency

๐Ÿ› ๏ธ Solution: Proxy Pattern + Caching
The Proxy Pattern introduces an intermediary that can add additional behavior (like caching) before delegating calls to the real service.

๐Ÿงฑ Implementation Steps:

  1. Define PaymentService Interface:
    • Method: String getTransactionStatus(String transactionId)
  2. RealPaymentService:
    • Connects to the real third-party API
  3. CachedPaymentService (Proxy):
    • Implements the same interface
    • Has a local cache (e.g., Map<String, CachedStatus>) with expiration logic
    • On getTransactionStatus call:
      • First check the cache
      • If present and valid, return cached result
      • Else, call RealPaymentService, cache result, then return

โœ… Benefits:

  • Cost Reduction: Fewer third-party invocations
  • Performance Boost: Faster response for cached data
  • Avoid Rate Limits: Reduces risk of hitting third-party rate caps
  • Code Transparency: Clients interact with the same interface

โ— Without Caching:

  • Redundant API calls
  • Higher latency
  • Higher costs and scalability issues

๐Ÿ“Œ Bonus Insight:

  • To apply caching without modifying RealPaymentService, use the Decorator Pattern.
  • Spring provides @Cacheable, which acts like a decorator and can be applied transparently.

๐Ÿ“Œ Interview Follow-Up Questions:

  • What caching strategies can be used (e.g., TTL, LRU)?
  • Difference between Proxy and Decorator?
  • How would you handle cache invalidation?

Leave a Comment