The Adapter Design Pattern is a structural pattern that allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces by converting the interface of a class into another interface that a client expects. This pattern is particularly useful when integrating new components or systems that have different interfaces.

Let's consider an example in a Ruby on Rails context where we have an existing class that fetches user data and a new system that expects data in a different format. We'll create an adapter to make these two systems work seamlessly.

Existing System: Fetching User Data

# Existing system with a different interface
class LegacyUserFetcher
  def fetch_user_info
    # Simulating fetching user data from the legacy system
    { name: 'John Doe', email: 'john@example.com' }
  end
end

New System: Expects Data in a Different Format

# New system expecting user data in a different format
class NewUserSystem
  def display_user(name:, email:)
    # Simulating displaying user data in the new system
    puts "User: #{name}, Email: #{email}"
  end
end

Adapter: Adapting the Existing System to the New System

# Adapter to make the existing system compatible with the new system
class UserAdapter
  def initialize(legacy_user_fetcher)
    @legacy_user_fetcher = legacy_user_fetcher
  end

  def adapt_and_display_user
    user_info = @legacy_user_fetcher.fetch_user_info
    display_user_in_new_system(user_info)
  end

  private

  def display_user_in_new_system(user_info)
    # Adapting the user data to the format expected by the new system
    name = user_info[:name]
    email = user_info[:email]

    # Using the new system to display the user
    new_system = NewUserSystem.new
    new_system.display_user(name: name, email: email)
  end
end

Client Code: Using the Adapter

# Client code using the adapter pattern
legacy_user_fetcher = LegacyUserFetcher.new
adapter = UserAdapter.new(legacy_user_fetcher)

# Using the adapter to fetch and display user data in the new system
adapter.adapt_and_display_user

In this example:

  • LegacyUserFetcher represents the existing system with a different interface.
  • NewUserSystem represents the new system expecting user data in a different format.
  • UserAdapter is the adapter that bridges the gap between the existing system and the new system. It takes the user data fetched from the existing system, adapts it to the format expected by the new system, and then uses the new system to display the user.

The key components of the Adapter Pattern are:

  1. Target (NewUserSystem): The interface that the client expects.
  2. Adaptee (LegacyUserFetcher): The class with an incompatible interface that needs to be adapted.
  3. Adapter (UserAdapter): The class that implements the target interface and uses an instance of the adaptee to achieve compatibility.

The Adapter Pattern is especially useful when integrating legacy systems with new systems or when dealing with third-party libraries with different interfaces. It allows for a smooth transition and integration without modifying the existing codebase extensively.