DEV Community

Nick Vasilev
Nick Vasilev

Posted on

Design Patterns: Adapter

Introduction

The Adapter design pattern helps objects with incompatible interfaces to collaborate. It's useful when integrating existing components into a new system, working with legacy code, or combining third-party libraries that have different interface requirements. By acting as a bridge, the Adapter allows otherwise incompatible classes to communicate without modifying their original code, promoting reusability and flexibility in software design.

Definition

The Adapter design pattern is a structural design pattern that allows object with incompatible interfaces collaborate.

What kind of problems can the Adapter design pattern solve?

  1. How can classes with incompatible interfaces work together?
  2. How can an alternative interface be provided to the class?
  3. How can a class be used if it doesn't provide the interface that a client requests?

The existing class can't be used because its interface is incompatible with the interface required by the client.

What solutions does the Adapter design pattern describe? b

  1. Define a separate adapter object that converts the incompatible interface of a class into the interface required by the client.
  2. Work with the adapter object for classes that don't have the required interface.

In this case, clients don't know whether they are working with the adapter object or the real object, because the adapter converts the incompatible interface into the required interface.

This pattern is similar to other design patterns, such as Facade, Decorator, and Proxy. You can read more about the differences in the Proxy article.

UML Diagram

The UML Diagram

The client requires the Target interface. However, this interface cannot reuse the Adaptee object because they have incompatible interfaces. Instead, the client works through the Adapter objects, which transform the Adaptee object to match the required protocol.

Examples

Let's imagine that we have legacy code in the project, and we need to reuse some functionality from the legacy codebase.

We have a class that defines how to fetch a user from the database. Let's imagine that we don't have access to the source code of the UserFetcher class and can't modify it.

final class UserFetcher {
  func user(_ userID: UUID, completion: @escaping (Result<User, Error>)) {
    // Fetches user data in some way and returns a result
    // Example: Fetch user from a legacy database or API
    completion(.success(User(id: userID, name: "John Doe", email: "john.doe@example.com")))
  }
}
Enter fullscreen mode Exit fullscreen mode

There is a new implementation that requires the use of Swift Concurrency with async/await.

protocol IUserFetcherAdapter {
  func user(_ userID: UUID) async throws -> User
}
Enter fullscreen mode Exit fullscreen mode

Here is the adapter class that uses the legacy implementation under the hood, thereby converting the incompatible interface into the interface required by the client.

final class UserFetcherAdapter: IUserFetcherAdapter {
  // MARK: Properties

  private let legacyUserFetcher: UserFetcher

  // MARK: Initialization

  init(legacyUserFetcher: UserFetcher) {
    self.legacyUserFetcher = legacyUserFetcher
  }

  // MARK: IUserDatabaseService 

  func user(_ userID: UUID) async throws -> User {
    try await withCheckedThrowingContinuation { continuation in
      legacyUserFetcher.user(userID) { result in
        switch result {
        case let .success(user):
          continuation.resume(returning: user)
        case let .failure(error):
          continuation.resume(throwing: error)
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Summary

The Adapter design pattern allows objects with incompatible interfaces to work together by acting as a bridge between them. It transforms the interface of an existing class into one that the client expects, enabling seamless integration without modifying the original code. This pattern is particularly useful when integrating legacy systems, third-party libraries, or different components within a software project. In this article, we explored the principles of the Adapter pattern, its implementation, and real-world use cases.

Top comments (0)