Home Simplified Adapter (Structural Pattern)
Post
Cancel

Simplified Adapter (Structural Pattern)

🌑 Why Adapter Pattern

Architect Spidey wants to enhance her movie review app by showing IMDb reviews. It’s a smart way to add more content and engage users.

But there’s a slight twist—IMDb provides the reviews in a format that doesn’t match what Spidey is already using. If she tries to plug it in directly, it’ll break things and require rewriting a lot of code.

She remembers visiting her grandparents, where a nearby spider from a different community spoke a language she couldn’t understand. Thankfully, a friend who knew both languages stepped in and translated, helping them understand each other.

Now, Architect Spidey hopes for something similar—a translator to help her app understand IMDb’s review format without changing the system.

That’s precisely what the Adapter Pattern does: it acts as a bridge between two incompatible interfaces, allowing them to work together seamlessly.

🌥️ Outline

An Adapter is a wrapper object that helps two incompatible objects talk to each other. It provides an interface friendly to one object and inside, it translates the call to match the other object’s way of working.

Adapter – A translator plug that makes two mismatched objects talk.

🌜 Adapter In Real World

When you travel to Europe, your phone charger plug doesn’t fit the wall sockets there. So, you use a travel adapter — a small tool that connects the two different systems and makes them work together.

🌕 Solution

Architect Spidey already uses a service that follows a MovieReviewService protocol, which returns a collection of MovieReview structs:

To fetch data from IMDb, she has a new service called IMDbReviewService, which returns a collection of IMDBReview objects matching the format received from the IMDb API

However, the formats don’t match, and integrating IMDB reviews directly into her app would mean changing existing logic. So, she applies the Adapter Pattern by creating a class called IMDBReviewAdapter.

Here’s what’s happening:

    The adapter wraps the IMDbReview object, handling all the conversion logic behind the scenes.
    The original IMDb data structure doesn’t even know it’s being adapted.
    The client (the movie review app) calls fetchMovieReviews(movieName) and receives reviews in the MovieReview format it already understands — no extra work required.

Just like a travel adapter helps your charger fit into a foreign socket, an adapter in software helps external data (like IMDb reviews) fit into your app without changing either.

Adapter Components

Adapter(Translator/Wrapper):
The Adapter is a wrapper class that makes two incompatible objects work together by converting the interface of one into the interface expected by the other.

In our example, IMDbReviewAdapter plays the role of the adapter. It wraps around an IMDbReviewService object (which has a different structure) and translates it into the MovieReview format that the app understands — just like a translator plug for mismatched devices.

Target:
This is what the client code is expecting. In terms of the translator, the language is understood by the existing system. In our example, the MovieReviewService struct is the Target.

Client(Consumer):
The Client is the part of the application that needs some service but only understands a specific format. It doesn’t know (or care) how the data comes in — it just wants it in the format it expects. In Spidey’s case, the movie review list page is the Client.

Adaptee:
The Adaptee is the class that already has the functionality, but its interface is not what the client expects. It cannot be used directly by the client. In this case, IMDbReviewService is the Adaptee.

🌟 Where To Apply

    Use Adapter to unify different formats for the same function
The Adapter pattern provides a uniform interface and acts as a translator for a client.
Imagine a news-reading application that pulls in articles from various news sources. Each source offers data in a different format: some use JSON, while others provide data in XML. The Adapter pattern can be applied here to convert each news source’s response into a uniform format. The application doesn’t need to worry about the specific format from sources.

    Use Adapter to standardize the interface
It might look similar to the point above but the key difference lies in why you’re applying the Adapter pattern. Instead of using the Adapter only when there’s a mismatch in data formats, you apply it as a standard integration layer for any third-party service, regardless of format compatibility. it can greatly enhance your system’s maintainability.
Imagine your application initially uses Apple Maps. All the logic and UI depend on its methods and data format. But later, due to bugs or limitations, you decide to switch to Google Maps.

🌓 To Gain Something, You Must Trade Off

Design patterns are solutions to common problems in software design. They provide a structured approach to solving issues, much like a blueprint. While it’s possible to solve a problem without using a design pattern, applying one offers better flexibility, scalability, and maintainability. However, each benefit comes with its trade-off. Let’s examine this concept in the context of the Adapter pattern.

Maintainability vs Complexity

The adapter class decouples the client code from a service & its dependency, so if the adaptee changes its format (JSON/XML) or behaviour, you only need to update the Adapter—not the entire application. This makes the system easier to maintain and scale dependencies without much risk.

Introducing an Adapter adds a new class to your system, which means there’s one more layer to understand. This can make the code slightly more complex to navigate, especially for new developers or when debugging.

Flexibility vs Extra Layer

The adapter pattern provides plug-and-play integration for external services. You can easily swap out one service for another without touching the main application code. This greatly improves flexibility and maintainability

While Adapters help incompatible objects or services work together, too many adapters can clutter your codebase. Think of it like a drawer full of travel plug adapters—very handy, but it quickly becomes messy and hard to manage when every device or region needs a slightly different plug. Likewise, having an adapter for every third-party service or object variation may lead to extra classes to create, track, and test.

Reusability vs Performance overhead

The Adapters allow developers to reuse existing code that might have incompatible interfaces. So instead of rewriting or modifying the original code, an adapter can translate its interface to match the new system’s requirements.

Adapters introduce an extra layer of indirection, which may affect performance, especially when dealing with large volumes of data transformation.

🌚 Some Interesting Fact

   Adapter & Facade Pattern
The adapter pattern allows allow two incompatible interfaces to work together. It acts as a translator or wrapper between objects with different interfaces. The Adapter pattern typically focuses on a single object (the adaptee) that needs to be made compatible with the system. It wraps this one object and converts its interface into one that the client code expects.
On the other hand, Facade provide a simplified interface to a complex system. It acts as a single entry point hiding internal complexities of framework or library. A Facade usually coordinates with multiple objects.

   Adapter & Proxy Pattern
The Proxy pattern adds a layer of control without changing the actual object.  The Proxy Pattern is like a security gate at the entrance of a building. It controls who can enter, logs their entry, and may even delay access until certain conditions are me. This pattern is useful for adding security, logging, caching, or lazy initialization.
On the other hand, the Adapter pattern does not introduce any custom behavior or restrictions. It simply translates or simplifies the interface to make an existing object compatible with what the client expects.

   Adapter & Mediator Pattern
The Adapter pattern helps two incompatible objects communicate by acting as a translator.
The Mediator pattern, on the other hand, acts as a central coordinator that manages communication between multiple objects. It is like an air traffic controller — ensuring that all planes (objects) communicate through a single point to avoid confusion and collision.

Code Example (Swift)

Target Interface:

1
2
3
protocol MovieReviewService {
    func fetchMovieReviews() -> [MovieReview]
}

Movie review:

1
2
3
4
5
6
struct MovieReview {
  var username: String
  var rating: Double 
  var review: String 
  var date: String
}

Adaptee Interface:

1
2
3
4
5
struct IMDbService {
    func fetchIMDbReviews(name: String) -> [IMDbReview] {
        //Fetch movie and return IMDbReview objects
    }
}
1
2
3
4
5
6
7
struct IMDbReview {
  var rating: Double
  var author: String 
  var review: String 
  var category: String
  var postedOn: String
}

The Adapter:

1
2
3
4
5
6
7
8
9
10
11
12
struct IMDbAdapter: MovieReviewService {
    let imdbService: IMDbService

    func fetchMovieReviews(name: String) -> [MovieReview] {
        let imdbReviews = imdbService.fetchIMDbReviews(name: name)
        return self.mapToMovieReviews(imdbReviews)
    }
    
    private func mapToMovieReviews(_ reviews: [IMDbReview]) -> [MovieReview] {
        //Map logic
    }
}

Client Code:

1
2
3
4
5
6
func displayMovieReview(name: String, service: MovieReviewService) {
    let reviews = service.fetchMovieReviews(name: name)
}
// Usage
let adapter = IMDbAdapter(imdbService: IMDbService())
displayMovieReview(name: "Avatar", service: adapter)
This post is licensed under CC BY 4.0 by the author.

Swift NonCopyable

Simplify Strategy (Behavioural Pattern)

Comments powered by Disqus.