Design Patterns in Android — Adapter
Design patterns are reusable solutions to the most commonly occurring software problems. They can speed up the development process by providing a proven way of resolving frequent issues.
In this series of articles I already wrote about:
Now it’s time for the Adapter!
Introduction
Adapter pattern is one of the structural patterns, which are used for class and object composition. This pattern is useful in situation when we have two different interfaces, we want to combine them and we can’t change them (unlike in the facade pattern). Thanks to this pattern we can build a bridge between them.
You can imagine Adapter pattern as the memory card adapter. We can have many of type cards (miniSD, microSD) and we’d like to read them all with a single card reader in the laptop. In this situation we can use adapter for memory card — a simple object that takes smaller card types and can be put into laptop as a one large card.
Adapter — example
Look at the example. Let’s assume we use library for Bluetooth in Android, which has its own implementation of Bluetooth Device — CoolBluetoothDevice
(clever name, isn’t it?). On the other side we have a Android framework class — BluetoothDevice
. They both are different and can’t be changed (it’s important). But we want to make them work together:
CoolBluetoothDevice
As you can see, it’s not that easy to fit B (CoolBluetoothDevice
) in A (a method which expects BluetoothDevice
). That’s when Adapter pattern comes! Let’s look at the example:
Let’s assume we wrote common method for connecting using CoolBluetoothDevice
objects, because that’s the object we’d like to use in the project instead of BluetoothDevice
.
private void connect(final CoolBluetoothDevice bluetoothDevice) {
...
}
We’d like to call this method like this:
BluetoothDevice bluetoothDevice;
CoolBluetoothDevice coolBluetoothDevice;
public void connectDevices()
connect(coolBluetoothDevice);
connect(bluetoothDevice); // we can't do it
}
Unfortunately we can’t use this method with an old implementation which is BluetoothDevice
. We need an adapter for it:
CoolBluetoothDevice, C — BluetoothDeviceAdapter
We need to create an adapter class, which takes BluetoothDevice
and makes it compatible as it’s CoolBluetoothDevice
(in our case we’ll just create new CoolBluetoothDevice
object with proper parameters and call connect()
method)
class BluetoothDeviceAdapter {
private final BluetoothDevice bluetoothDevice;
public BluetoothDeviceAdapter(final BluetoothDevice
bluetoothDevice) {
this.bluetoothDevice = bluetoothDevice;
}
public void connect(final BluetoothDevice bluetoothDevice) {
CoolBluetoothDevice coolBluetoothDevice =
new CoolBluetoothDevice(
bluetoothDevice.getAddress(),
bluetoothDevice.getType(),
bluetoothDevice.getName(),
bluetoothDevice.getUuids());
connect(coolBluetoothDevice);
}
We created an adapter class which packs the old implementation (BluetoothDevice
) and returns a new one(CoolBluetoothAdapter
). This way we can use connect()
method for both types:
BluetoothDevice bluetoothDevice;
CoolBluetoothDevice coolBluetoothDevice;
public void connectDevices()
connect(coolBluetoothDevice); BluetoothDeviceAdapter bluetoothDeviceAdapter = new
BluetoothDeviceAdapter(bluetoothDevice);
bluetoothDeviceAdapter.connect(bluetoothDevice);
}
There are many other ways to extend this pattern, for example:
- we can have common abstract implementation of both types (e.g. base class called
Device
) and handle bidirectional changes fromBluetoothDevice
toCoolBluetoothDevice
and opposite — this way we don’t need to care which one type do we have now (helpful to removeinstanceOf
checks) - we can have adapter for more types — this way we’ll need extract
connect()
method to the interface
This pattern is very easy and useful when you try to extend existing implementation or combine it with the code from the library which can’t be modified. This pattern can be especially helpful in Android, where we have a framework that can’t be modified and other libraries with its own types.
That’s all! Stay tuned for more!