Bird
Raised Fist0

Adapter Pattern - Making Incompatible Interfaces Work Together

LLDeasyInterview freq: common
Choose your preparation mode4 modes available
🏗️
Adapter Pattern - Making Incompatible Interfaces Work Together
Enable collaboration between incompatible interfaces by wrapping one with an adapter.
easyDesign Patterns - AppliedFreq: common
Prerequisites:OOP BasicsInterfaces and Abstract Classes in JavaDesign Patterns Fundamentals
⚙️
Functional Requirements
  • Adapter must implement TargetInterface to be used transparently by Client.
  • Adapter must hold a reference to Adaptee and delegate calls appropriately.
  • Adapter must translate method calls and data formats between TargetInterface and Adaptee.
  • Adapter should allow extension without modifying existing Adaptee or TargetInterface classes (Open/Closed Principle).
  • Adapter must be thread-safe if used concurrently by multiple clients.
  • Adapter should handle null or invalid inputs gracefully.
  • Adapter should not expose Adaptee's interface directly to Client.
👤
Use Cases
  • Client calls TargetInterface methods expecting a certain behavior.
  • Adapter converts calls from TargetInterface to Adaptee's incompatible interface.
  • Adaptee performs operations in its own incompatible interface.
  • Client remains unaware of Adaptee's interface differences.
  • Adapter maintains any necessary state to translate between interfaces.
🧑‍💻
Actors
  • Client
  • TargetInterface
  • Adaptee
  • Adapter
❓
Clarifying Questions to Ask
  • How many concurrent threads will access the Adapter? Is thread safety required?
  • Should the Adapter cache any data or maintain state between calls?
  • Are there any specific error handling or fallback strategies expected?
  • Is the Adapter expected to support multiple Adaptee instances or just one?
  • What is the expected lifecycle of the Adapter relative to the Adaptee?
  • Are there any constraints on the Adapter's memory footprint or latency?
  • Should the Adapter support dynamic switching of Adaptee instances at runtime?
Out of Scope
✗ Modifying Adaptee or TargetInterface source code.
✗ Implementing multiple adapters for multiple incompatible interfaces in one class.
✗ Performance optimization beyond thread safety and correctness.
⚡
QPS Calculation
read qps1000
write qps100
🖥️
Server Estimate
2
🔢
Enumerations
enum AdapterType
CLASS_ADAPTER | OBJECT_ADAPTER
Defines whether the adapter uses inheritance (class adapter) or composition (object adapter). This enum prevents typo bugs and enables exhaustive switch statements in adapter factory implementations.
enum AdapterStatus
INITIALIZED | ACTIVE | ERROR
Tracks the lifecycle state of the adapter instance. Using enum avoids string literals and supports state-based logic with exhaustive switch.
📦
TargetInterface
0 fields
Defines the interface expected by the Client. Declares methods that the Adapter must implement to be compatible.
Why thisUsing a Java interface allows multiple implementations and enforces method contracts without implementation details.
Why notAbstract classes would force inheritance and limit flexibility; interfaces provide cleaner abstraction.
public void request()Method that Client calls expecting a specific behavior.
📦
Adaptee
0 fields
Defines an incompatible interface with specific methods that need adapting.
Why thisRepresents existing legacy or third-party code with incompatible interface; cannot be changed.
Why notModifying Adaptee is out of scope and breaks encapsulation; adapter pattern avoids this.
public void specificRequest()Method with incompatible signature that performs the actual operation.
📦
Adapter
2 fields
private final Adaptee adapteeprivate volatile AdapterStatus status
Implements TargetInterface and translates calls to Adaptee's interface. Maintains state to track adapter lifecycle and ensure thread safety.
Why thisUsing composition to hold Adaptee allows flexible adaptation without inheritance constraints.
Why notClass adapter via inheritance is less flexible and Java does not support multiple inheritance; composition preferred.
public synchronized void request()Overrides TargetInterface method, translates call to adaptee.specificRequest(), ensures thread-safe execution.
public AdapterStatus getStatus()Returns current adapter lifecycle status. Volatile ensures visibility across threads.
📦
Client
1 fields
private TargetInterface target
Uses TargetInterface without knowledge of Adaptee. Calls adapter methods transparently.
Why thisClient depends on abstraction (TargetInterface) to remain decoupled from Adaptee implementation.
Why notDirectly using Adaptee would break encapsulation and coupling.
public void execute()Calls target.request() to perform operation.
📦
AdapterFactory
0 fields
Creates Adapter instances based on AdapterType. Encapsulates adapter creation logic enforcing Open/Closed Principle.
Why thisFactory method centralizes adapter creation and supports extension without modifying client code.
Why notDirect instantiation scatters creation logic and violates Open/Closed Principle.
public static TargetInterface createAdapter(AdapterType type, Adaptee adaptee)Returns an Adapter instance of specified type wrapping the given Adaptee.
🔗
Relationships
Adapter◆──►Adaptee[1]
compositionAdapter owns Adaptee reference but Adaptee can exist independently
Adapter──►TargetInterface[1]
realizationAdapter implements TargetInterface interface
Client──►TargetInterface[1]
associationClient holds reference to TargetInterface but does not own it
AdapterFactory──►Adapter[1]
associationFactory creates Adapter instances but does not own them
🗄️
AdapterState
PK: adapterId
adapterId UUIDtimestamp TIMESTAMPstatus VARCHAR
Stores lifecycle status changes of Adapter instances over time.
🗄️
AdapteeDataCache
PK: adapteeId
adapteeId UUIDcacheKey VARCHARcacheValue BLOB
Caches data translated by Adapter for performance optimization.
🔌
CreateAdapter
Request
{
  "adapteeId": "UUID",
  "adapterType": "AdapterType"
}
Response
{
  "adapterId": "UUID",
  "status": "AdapterStatus"
}
Creates an Adapter instance wrapping the specified Adaptee.
🔌
InvokeRequest
Request
{
  "adapterId": "UUID"
}
Response
{
  "success": "boolean",
  "errorMessage": "String"
}
Invokes the request method on the Adapter.
🔌
GetAdapterStatus
Request
{
  "adapterId": "UUID"
}
Response
{
  "status": "AdapterStatus"
}
Retrieves current lifecycle status of the Adapter.

Practice

(1/5)
1. A RateLimiter class has a private constructor and a static synchronized getInstance() method returning a single instance. Which design pattern is applied and why is this the correct choice for a token bucket rate limiter?
easy
A. Builder pattern to configure token bucket parameters fluently
B. Factory pattern to create multiple rate limiter instances with different limits
C. Singleton pattern to ensure a single shared token bucket across threads
D. Prototype pattern to clone rate limiter instances for each client

Solution

  1. Step 1: Identify the pattern from constructor and getInstance()

    The private constructor and static synchronized getInstance() method indicate Singleton pattern, ensuring only one instance exists.
  2. Step 2: Why Singleton fits token bucket rate limiter

    Singleton ensures a single shared token bucket state across threads, preventing inconsistent token counts.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Singleton enforces single shared instance for thread-safe token bucket [check]
Hint: Private constructor + static synchronized getInstance() -> Singleton [check]
Common Mistakes:
2. In the Online Shopping Cart system, the Cart class has a private constructor and a static synchronized getInstance() method. Which design pattern is applied here, and why is this the correct use case?
easy
A. Factory pattern, because it controls object creation centrally.
B. Singleton pattern, to ensure only one Cart instance exists per user session.
C. Builder pattern, to construct Cart objects step-by-step.
D. Prototype pattern, to clone Cart instances efficiently.

Solution

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

    The private constructor prevents external instantiation, and the static synchronized getInstance() method controls instance creation, which matches the Singleton pattern.
  2. Step 2: Reason why Singleton fits Cart

    Ensuring only one Cart instance per user session avoids inconsistent states and duplicate carts, which is the intended use case here.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Private constructor + synchronized getInstance() -> Singleton pattern [check]
Hint: Private constructor + static getInstance() -> Singleton [check]
Common Mistakes:
3. The Car Rental System currently uses a FeeStrategy interface for pricing. To add a WeekendFeeStrategy without modifying existing classes, which SOLID principle is being applied, and what is the minimum required change?
medium
A. Single Responsibility Principle; modify the existing FeeStrategy implementations to include weekend logic.
B. Open/Closed Principle; create a new WeekendFeeStrategy class implementing FeeStrategy without changing existing code.
C. Liskov Substitution Principle; override existing FeeStrategy methods to handle weekend fees.
D. Dependency Inversion Principle; inject WeekendFeeStrategy into existing classes by modifying their constructors.

Solution

  1. Step 1: Identify the SOLID principle for extensibility

    Open/Closed Principle (OCP) states software entities should be open for extension but closed for modification.
  2. Step 2: Determine minimal change to add WeekendFeeStrategy

    To avoid modifying existing classes, inject the new WeekendFeeStrategy via dependency injection, applying Dependency Inversion Principle (DIP) to depend on abstractions.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Inject new strategy without modifying existing classes -> DIP [check]
Hint: Add new strategy via injection, not modifying existing code [check]
Common Mistakes:
4. You want to add a WeekendFeeStrategy to calculate notification fees differently on weekends. According to SOLID principles, what is the minimum change required in the current design that uses a FeeStrategy interface?
medium
A. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without modifying existing classes.
B. Add a boolean weekend flag to FeeStrategy interface and update all implementations accordingly.
C. Modify the existing FeeStrategy implementation to add weekend logic with if-else checks.
D. Create a WeekendFeeStrategy subclass extending the existing FeeStrategy implementation.

Solution

  1. Step 1: Identify applicable SOLID principle

    The Open-Closed Principle (OCP) requires classes to be open for extension but closed for modification.
  2. Step 2: Evaluate options against OCP

    Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without modifying existing classes. adds a new class implementing FeeStrategy without modifying existing code, adhering to OCP. Others modify existing classes or interfaces, violating OCP.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    New strategy class extends behavior without modifying existing code [check]
Hint: OCP: extend with new class, don't modify existing [check]
Common Mistakes:
5. In the Index class, without synchronizing the insertEntry() method, what exact sequence of Thread 1 and Thread 2 causes duplicate keys in the index?
medium
A. Thread 1 checks if key exists -> false, context switch; Thread 2 checks if key exists -> false; both insert the same key concurrently.
B. Thread 1 inserts key and commits; Thread 2 reads stale index snapshot and inserts duplicate key.
C. Thread 1 inserts key, Thread 2 waits until Thread 1 finishes, then inserts duplicate key.
D. Thread 1 deletes key; Thread 2 inserts key before deletion completes, causing inconsistency.

Solution

  1. Step 1: Identify the check-then-act race condition

    Without synchronization, both threads can check for key existence simultaneously and see it as absent.
  2. Step 2: Trace thread interleaving causing duplicate insert

    Thread 1 checks key -> false, context switch; Thread 2 checks key -> false; both proceed to insert the same key, causing duplicates.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Concurrent check-then-act without sync -> duplicate keys inserted [check]
Hint: Check-then-act gap -> both see key absent -> duplicate insert [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