A photo of a power adapter

๐Ÿ”Œ Exploring the Adapter Design Pattern in Ruby


author

Author: David Martinez

Created: November 23, 2024 - Updated: November 24, 2024

Read Time: 10 min

Ruby

Welcome! Today, letโ€™s uncover the secrets of the Adapter design pattern. ๐Ÿ› ๏ธ We'll explore its purpose, applications, and see a real-world example in Ruby that demonstrates its power in action.

Unveiling the Adapter Pattern ๐Ÿ”Œ

The Adapter is a structural design pattern that enables two incompatible interfaces to work together. Think of it as a translator that bridges the gap between systems or classes that otherwise couldnโ€™t collaborate.

This pattern is especially helpful when integrating third-party libraries or legacy systems with your codebase.

When to Use the Adapter Pattern?

๐Ÿ”ธ Use the Adapter pattern when you want to reuse an existing class but its interface is incompatible with the rest of your code.

๐Ÿ”ธ It's particularly useful when dealing with third-party APIs or legacy systems that cannot be changed.

Problem Scenario

Imagine youโ€™re building a payment system that works with PayPal. Now, a client requests integration with Stripe. Unfortunately, the APIs for PayPal and Stripe differ in how they process payments. Modifying the existing code for every new API would lead to high maintenance costs and potential bugs.

Solution

The Adapter pattern provides a bridge between your application and the Stripe API, allowing it to seamlessly integrate without altering the existing PayPal implementation.

By creating an adapter for Stripe, you make its interface compatible with the interface your application already uses.

UML Diagram

In this example, the Adapter pattern is applied to unify the interaction between PayPal and Stripe.

๐Ÿ”น The Target Interface (PaymentGateway) defines the standard interface expected by the client code.

๐Ÿ”น The Adaptee (StripeGateway) has an incompatible interface.

๐Ÿ”น The Adapter (StripeAdapter) translates requests from the client into a format the Adaptee can understand.

How does it Work?

1๏ธโƒฃ Define a target interface that the client code uses (PaymentGateway).

2๏ธโƒฃ Implement the target interface in the adapter class (StripeAdapter) and delegate calls to the Adaptee (StripeGateway).

3๏ธโƒฃ The client code interacts with the adapter as if it were the target interface, without worrying about the underlying differences.

๐Ÿ’ก Note: This pattern is excellent for making legacy or third-party code work with your application.

Why Embrace the Flyweight Pattern?

๐Ÿ”ฎ Facilitates integration with third-party libraries or APIs.

๐Ÿ”ฎ Minimizes the need for changes in existing code.

๐Ÿ”ฎ Promotes code reusability by allowing systems with incompatible interfaces to collaborate.

๐Ÿ”ฎ Provides a scalable approach for handling future integrations.

Show me the code

# Payment Gateway interface
class PaymentGateway
def pay(amount)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end
# Client-facing class
class PaymentProcessor
def initialize(payment_gateway)
@payment_gateway = payment_gateway
end
def process_payment(amount)
@payment_gateway.pay(amount)
end
end
# Existing PayPal integration
class PayPalGateway < PaymentGateway
def pay(amount)
"Paying #{amount} with PayPal"
end
end
# Stripe's API with a different interface
class StripeGateway
def charge(amount_in_cents)
"Charging #{amount_in_cents / 100.0} with Stripe"
end
end
# Adapter for Stripe to work with our system
class StripeAdapter < PaymentGateway
def initialize(stripe_gateway)
@stripe_gateway = stripe_gateway
end
def pay(amount)
amount_in_cents = (amount * 100).to_i
@stripe_gateway.charge(amount_in_cents)
end
end
# Example Usage
def main
# Using PayPal
paypal_gateway = PayPalGateway.new
payment_processor = PaymentProcessor.new(paypal_gateway)
puts payment_processor.process_payment(100)
# Output: "Paying 100 with PayPal"
# Using Stripe through the adapter
stripe_gateway = StripeGateway.new
stripe_adapter = StripeAdapter.new(stripe_gateway)
payment_processor = PaymentProcessor.new(stripe_adapter)
puts payment_processor.process_payment(100)
# Output: "Charging 100.0 with Stripe"
end
main

Conclusion ๐Ÿ”–

The Adapter pattern provides a powerful way to integrate systems with incompatible interfaces.

๐Ÿ”บ By leveraging this pattern, you can connect legacy systems or third-party APIs with your application effortlessly.

๐Ÿ”บ It reduces the overhead of rewriting or heavily modifying existing code.

๐Ÿ”บ The Adapter pattern promotes reusability and simplifies future integrations.

๐Ÿ”บ It adheres to the Open/Closed Principle by enabling extensions (e.g., new APIs) without altering existing code.

Join the Quest!

๐Ÿ’ป You can find this and other design patterns here ๐Ÿ“š