Adapter vs Facade vs Proxy - Structural Pattern Comparison - Watch the Algorithm Execute, Step by Step
Watching each pattern's mechanism unfold visually helps you grasp their distinct roles and how they simplify or control interactions in complex systems.
Step 1/10
·Active fill★Answer cell
Setup of the adaptee class which the Adapter will wrap.
PaymentGateway
+make_payment(amount)()
Adapter pattern composition: Adapter holds a reference to adaptee.
PaymentGateway
+make_payment(amount)()
PaymentAdapter
−gateway: PaymentGateway
+pay(amount)()
PaymentAdapter ◆ PaymentGateway
Adapter delegates method call to adaptee with interface translation.
Proxy pattern composition: Proxy holds reference to real subject and adds control.
Database
+query()()
DatabaseProxy
−db: Database
−authenticated: bool
+authenticate(user)()
+query()()
DatabaseProxy ◆ Database
Proxy updates internal state to allow access.
DatabaseProxy
−authenticated: bool
+authenticate(user)()
Proxy forwards method call to real subject after access check.
DatabaseProxy
−authenticated: bool
+query()()
Database
+query()()
DatabaseProxy ◆ Database
Key Takeaways
✓ Adapter pattern changes the interface of an existing class to make it compatible with clients.
This insight is hard to see from code alone because the adapter's role is subtle and involves delegation with interface translation.
✓ Facade pattern simplifies complex subsystem interactions by providing a unified interface.
Seeing the facade coordinate multiple subsystem calls visually clarifies how it reduces client complexity.
✓ Proxy pattern controls access to a real subject by adding authentication or other checks before forwarding requests.
The proxy's control logic is often invisible in code but becomes clear when watching the access decision and delegation steps.
Practice
(1/5)
1. You are designing a system where multiple unrelated classes must guarantee implementation of certain methods but share no common code. Which abstraction mechanism is most appropriate to enforce this contract?
easy
A. Use an abstract class to define the methods and provide partial implementation.
B. Use an abstract class only if all classes share a common ancestor.
C. Use a concrete class and rely on inheritance for code reuse.
D. Use an interface to declare the methods without any implementation.
Solution
Step 1: Identify the need for a contract without shared code
Interfaces define method signatures without implementation, perfect for unrelated classes needing a common contract.
Step 2: Why abstract classes are less suitable here
Abstract classes provide partial implementation and require a common ancestor, which unrelated classes lack.
Step 3: Why concrete classes and inheritance don't fit
Concrete classes imply implementation and inheritance assumes a hierarchy, which is not guaranteed.
Final Answer:
Option D -> Option D
Quick Check:
Interfaces enforce contracts without imposing inheritance or shared code.
Hint: Use interfaces when only a contract is needed, abstract classes when sharing code.
Common Mistakes:
Assuming abstract classes are always better for abstraction.
Confusing contract enforcement with code reuse.
Believing unrelated classes can share an abstract class.
2. In the object-oriented design of a Snake and Ladder game, which component is primarily responsible for managing the state transitions of a player's position after a dice roll?
easy
A. The Dice class, since it generates the number that determines movement
B. The Player class, as it holds the current position and updates it directly
C. The Board class, because it contains the snakes and ladders and applies their effects
D. The GameController class, which orchestrates the game flow and updates player positions accordingly
Solution
Step 1: Understand the role of Dice
The Dice only generates a random number; it does not manage state transitions.
Step 2: Consider Player class responsibilities
Player holds position but should not decide how to update it considering snakes or ladders.
Step 3: Analyze Board class role
Board knows snakes and ladders but does not manage player state transitions directly.
Step 4: Role of GameController
GameController coordinates dice roll, queries Board for snakes/ladders, and updates Player position accordingly.
Final Answer:
Option D -> Option D
Quick Check:
GameController centralizes state transitions, ensuring separation of concerns.
Hint: GameController orchestrates state changes, not Dice or Player alone [OK]
Common Mistakes:
Thinking Dice manages player position
Assuming Player updates position without Board's input
Believing Board directly changes player state
3. Given the following code snippet, what will be printed when executing the last two lines?
from abc import ABC, abstractmethod
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount):
pass
class CreditCardStrategy(PaymentStrategy):
def pay(self, amount):
print(f"Processing credit card payment of ${amount}")
class UPIStrategy(PaymentStrategy):
def pay(self, amount):
print(f"Processing UPI payment of ${amount}")
class PaymentStrategyFactory:
@staticmethod
def get_strategy(method):
if method == 'CreditCard':
return CreditCardStrategy()
elif method == 'UPI':
return UPIStrategy()
else:
raise ValueError('Invalid payment method')
class PaymentProcessor:
def __init__(self, strategy: PaymentStrategy):
self.strategy = strategy
def pay(self, amount):
self.strategy.pay(amount)
processor = PaymentProcessor(PaymentStrategyFactory.get_strategy('UPI'))
processor.pay(100)
easy
A. Raises ValueError: Invalid payment method
B. Processing credit card payment of $100
C. Processing net banking payment of $100
D. Processing UPI payment of $100
Solution
Step 1: Trace strategy selection
The factory method get_strategy('UPI') returns an instance of UPIStrategy.
Step 2: Trace payment method call
The pay method of UPIStrategy prints "Processing UPI payment of $100".
Final Answer:
Option D -> Option D
Quick Check:
Correct strategy instance leads to correct output [OK]
Hint: Factory returns correct strategy instance for method [OK]
Common Mistakes:
Confusing strategy returned or output string
4. Identify the bug in the following Python code implementing deep copy for the Profile class:
import copy
class Profile:
def __init__(self, name, scores):
self.name = name
self.scores = scores
def __deepcopy__(self, memo):
new_name = self.name # Bug here
new_scores = copy.deepcopy(self.scores, memo)
return Profile(new_name, new_scores)
original = Profile('Alice', [10, 20])
copy_obj = copy.deepcopy(original)
copy_obj.scores.append(30)
print(original.scores)
medium
A. Line assigning new_name = self.name does not deepcopy the name string
B. Line assigning new_scores = copy.deepcopy(self.scores, memo) incorrectly copies scores
C. The __init__ method does not initialize scores properly
D. The return statement returns a new Profile instead of modifying self
Solution
Step 1: Examine __deepcopy__ method
The line new_name = self.name copies the reference to the name string instead of deep copying it.
Step 2: Understand impact
Strings are immutable in Python, so shallow copy is usually safe, but if name were a mutable object, this would cause shared references and bugs. Proper deep copy should be used for consistency.
Final Answer:
Option A -> Option A
Quick Check:
Only new_name assignment lacks deepcopy -> subtle bug [OK]
Hint: All nested fields must be deep copied consistently [OK]
Common Mistakes:
Forgetting to deepcopy all nested fields, assuming immutables are safe
5. If the Library Management System must support reserving books that are currently loaned out, which design change best handles the edge case where multiple users reserve the same book simultaneously?
hard
A. Let the User class maintain their own reservation list independently without central coordination.
B. Allow multiple reservations by updating the book's status to 'reserved' without tracking order.
C. Add a reservation queue in the LoanManager and process reservations in FIFO order with atomic updates.
D. Implement a polling mechanism where users repeatedly check book availability without reservations.
Solution
Step 1: Why a reservation queue?
FIFO queue ensures fairness and order in handling multiple reservations.
Step 2: Why not multiple reservations without order?
Allow multiple reservations by updating the book's status to 'reserved' without tracking order. causes ambiguity and race conditions in fulfilling reservations.
Step 3: Why not user-maintained reservations?
Let the User class maintain their own reservation list independently without central coordination. lacks centralized control, risking inconsistent reservation states.
Step 4: Why not polling without reservations?
Implement a polling mechanism where users repeatedly check book availability without reservations. leads to poor user experience and race conditions.
Final Answer:
Option C -> Option C
Quick Check:
Centralized, ordered reservation management with atomic updates handles concurrency and fairness.
Hint: Use a centralized FIFO reservation queue to handle concurrent reservations fairly [OK]
Common Mistakes:
Ignoring reservation order and fairness
Decentralizing reservation tracking
Relying on user polling instead of system-managed reservations