Adapter
The main goal of an Adapter, or Wrapper, as it's sometimes called, is to convert one interface to another interface. In the physical world, the best example would be an electrical plug Adapter, or a USB Adapter. Imagine yourself in a hotel room in the late evening, with 7% battery left on your phone. Your phone charger was left in the office, at the other end of the city. You only have an EU plug charger with a USB mini cable. But your phone is USB type-C and near all your outlets are of type-A. What do you do? So, now that we understand a bit better what adapters are for in the physical world, let's see how we can apply the same in code. Let's start with interfaces:
interface UsbTypeC
interface UsbMini
interface EUPlug
interface USPlug Now we can declare a phone and a power outlet: // Power outlet exposes USPlug interface
fun powerOutlet() : USPlug {
return object : USPlug {}
}
fun cellPhone(chargeCable: UsbTypeC) {
}
Our charger is wrong in every way, of course: // Charger accepts EUPlug interface and exposes UsbMini interface
fun charger(plug: EUPlug) : UsbMini {
return object : UsbMini {}
}
Here we get the following errors: Type mismatch: required EUPlug, found USPlug: charger(powerOutlet())
Type mismatch:
required UsbTypeC, found UsbMini: cellPhone(charger(powerOutlet()))
Different adapters So, we need two types of adapters. In Java, you would usually create a pair of classes for that purpose. In Kotlin, we can replace those with extension functions. We could adopt the US plug to work with the EU plug by using the following extension function:
fun USPlug.toEUPlug() : EUPlug {
return object : EUPlug {
// Do something to convert
}
}
We can create a USB Adapter between mini USB and type-C USB in a similar way:
fun UsbMini.toUsbTypeC() : UsbTypeC {
return object : UsbTypeC {
// Do something to convert
}
}
And finally, we get back online by combining all those adapters together:
cellPhone(
charger(
powerOutlet().toEUPlug()
).toUsbTypeC()
)
As you can see, we don't need to compose one object inside the other to adapt them. Nor, luckily, do we need to inherit both interface and implementation. With Kotlin, our code stays short and to the point.
Adapters in the real world
You've probably encountered those adapters too. Mostly, they adapt between concepts and implementations. For example, let's take the concept of collection versus the concept of a stream:
val l = listOf("a", "b", "c")
fun <T> streamProcessing(stream: Stream<T>) {
// Do something with stream
}
You cannot simply pass a collection to a function that receives a stream, even though it may make sense: streamProcessing(l) // Doesn't compile
Luckily, collections provide us with the .stream() method:
streamProcessing(l.stream()) // Adapted successfully
Caveats of using adapters Did you ever plug a 110v appliance into a 220v socket through an Adapter, and fry it totally? That's something that may also happen to your code, if you're not careful.
The following example, which uses another Adapter, compiles well:
fun <T> collectionProcessing(c: Collection<T>) {
for (e in c) {
println(e)
}
}
val s = Stream.generate { 42 }
collectionProcessing(s.toList()) But it never completes, because Stream.generate() produces an infinite list of integers.
So, be careful, and adapt this pattern wisely.
Code Example :
AdpaterDesignPattern.kt
import java.util.stream.Stream
fun <T> collectionProcessing(c: Collection<T>) {
for (e in c) {
println(e)
}
}
fun <T> streamProcessing(stream: Stream<T>) {
// Do something with stream
}
fun USPlug.toEUPlug(): EUPlug {
return object : EUPlug {
// Do something to convert
}
}
fun UsbMini.toUsbTypeC(): UsbTypeC {
return object : UsbTypeC {
// Do something to convert
}
}
// Power outlet exposes USPlug interface
fun powerOutlet(): USPlug {
return object : USPlug {}
}
// Charger accepts EUPlug interface and exposes UsbMini interface
fun charger(plug: EUPlug): UsbMini {
return object : UsbMini {}
}
fun cellPhone(chargeCable: UsbTypeC) {
}
interface UsbTypeC
interface UsbMini
interface EUPlug
interface USPlug
main.kt
import java.util.stream.Stream
fun main(args: Array<String>) {
// Adapter Design Pattern test
cellPhone(charger(powerOutlet().toEUPlug()).toUsbTypeC())
val l = listOf("a", "b", "c")
streamProcessing(l.stream())
val s = Stream.generate { 42 }
println("Collecting elements")
collectionProcessing(s.toList())
}
No comments:
Post a Comment