Adapter
Also known as¶
- Wrapper
Intent¶
Convert the interface of a class into another interface the clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
Explanation¶
Real-world example¶
Consider that you have some pictures on your memory card, and you need to transfer them to your computer. To transfer them, you need some kind of adapter that is compatible with your computer ports so that you can attach a memory card to your computer. In this case the card reader is an adapter.
In plain words¶
Adapter pattern lets you wrap an otherwise incompatible object in an adapter to make it compatible with another class.
Wikipedia says¶
In software engineering, the adapter pattern is a software design pattern that allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code.
sequenceDiagram
participant Captain
participant FishingBoatAdapter
participant FishingBoat
Captain->>FishingBoatAdapter: row()
FishingBoatAdapter->>FishingBoat: sail()
FishingBoat-->>FishingBoatAdapter: done
FishingBoatAdapter-->>Captain: done
Programmatic Example¶
Consider a captain that can only use rowing boats and cannot sail at all.
First, we have interfaces RowingBoat and
FishingBoat:
interface RowingBoat {
fun row()
}
internal class FishingBoat {
fun sail() {
logger.info("The fishing boat is sailing")
}
}
And captain expects an implementation of RowingBoat
interface to be able to move:
Now let's say the pirates are coming and our captain needs to escape but there is only a fishing boat available. We need to create an adapter that allows the captain to operate the fishing boat with his rowing boat skills.
internal class FishingBoatAdapter(
private val boat: FishingBoat,
) : RowingBoat {
override fun row() {
boat.sail()
}
}
And now the Captain can use the FishingBoat to
escape the pirates.
val fishingBoat = FishingBoat()
val adapter = FishingBoatAdapter(fishingBoat)
val captain = Captain(adapter)
captain.row()
Alternatively, we can use Kotlin's extension function for our adapter:
And now the Captain can use the FishingBoat more
idiomatically:
Class diagram¶
classDiagram
class Captain {
-rowingBoat RowingBoat
+row()
}
class FishingBoat {
+sail()
}
class FishingBoatAdapter {
-boat FishingBoat
+row()
}
class RowingBoat {
<<interface>>
+row()
}
FishingBoatAdapter --> FishingBoat : boat
Captain --> RowingBoat : rowingBoat
FishingBoatAdapter ..|> RowingBoat
class FishingBoatExtFunctionAdapter {
<<extension>>
+toRowingBoat(FishingBoat) RowingBoat
}
FishingBoatExtFunctionAdapter ..> FishingBoat
FishingBoatExtFunctionAdapter ..> RowingBoat
Applicability¶
Use the Adapter pattern when:
- You want to use an existing class and its interface does not match the one you need.
- You want to create a reusable class that cooperates with unrelated or unforeseen classes.
- You need to use several existing subclasses, but it's impractical to adapt their interface by subclassing everyone.
Consequences¶
Benefits:
- Allows reuse of existing classes even when their interfaces are incompatible.
- Provides flexibility by introducing only one additional object without changing the adaptee.
Trade-offs:
- An object adapter makes it harder to override adaptee behavior; it requires subclassing the adaptee.
- Adds a layer of indirection, which can complicate debugging.
Related Patterns¶
- Decorator: Changes responsibilities without changing the interface, while Adapter changes the interface.
- Bridge: Separates abstraction from implementation; Adapter makes unrelated classes work together.