Design patterns: Adapter – With Kotlin examples

Design patterns split into three main categories Creational, Structural and Behavioral, we explored the creational patterns and now we will move on to the second category the Structural patterns. These patterns focus on how to assemble objects and classes to constry larger structures with simpler and more flexible relationships. And as a beginning of our journey to explore these patterns I will talk about the first one; Adapter pattern (also called Wrapper).

Adapter pattern is basically an adapter!! In the real life we use adapters to connect between two objects when we need to get an advantage from one of them and it is not compatible with the second one, for example the adapter that used to connect between power plug and socket with different design. Now let’s see the adapter in the software world. The Gang of Four define adapter’s intent in Design patterns book as:

Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.

According to this definition, if your client deals with classes that have a specific interface and you need to use an existing class with different interface that is not compatible with the client, then you can use adapter class to wrap the existing class and make it compatible with the client without making any changes in the existing class or the client. To illustrate, let’s see an example.

If we have an application that converts any currency to USD and we need to use a new library that converts currencies to EUR and lets assume the new library uses ISO codes to define currencies. But in our application we need to pass the country name that uses this currency . In this case we can adjust the library or the client to be compatible. But the new changes may cause some problems. Another problem we will face is that we might not be able to change the new library’s source code!

Adapter pattern in this situation can take a main role by defining a class that inherits client interface and new library so it is compatible with each of them and it will be responsible of any needed translation and conversions between the client and the new library. So it will take the request from the client and prepare the ISO code of requested country’s currency and pass it to the library. Therefore this class wrap the new library and add its feature to the client without making any part aware of what happening.

The new class, client interface and new library are called Adapter, Target and Adaptee respectively. The following UML diagram shows adapter pattern’s structure

Target:

Defines the domain-specific interface that Client uses.

Adaptee:

Defines an existing interface that needs adapting.

Adapter:

Adapts the interface of Adaptee to the Target interface.

Actually the above example describes one type of adapter a class adapter which inherits the adaptee interface and adapt it to the target interface, there is another type called object adapter that defines an adaptee object and uses it without needing to inherit any adaptee class. Each type has its pros and cons. A class adapter has the ability to override the Adaptee’s behavior since it is a subclass of Adaptee but on the the other hand it restricts on adapt parent class, and its subclasses will need another adapters to work with them. Vice versa an object adapter can work with the Adaptee and its all subclasses and has the ability of adding functionality to them at once but in the same time it can not override Adaptee’s behavior, therefor there is no a better type it is just depends on your situation and what you need and what language you use.

The following diagram illustrates object adapter’s structure

Implementation:

Let’s take our currency converter example and implement it using Kotlin language and object adapter

Here the currency converter interface (Targert) and its subclass CurrencyToDollarConverter

interface CurrencyConverter{
    fun convertCurrency(countryName: String)
}

class CurrencyToDollarConverter: CurrencyConverter{
    override fun convertCurrency(countryName: String) {
        console.log("Dollar Currency")
    }
}

 

CurrencyToEuroConverter takes an adaptee role

class CurrencyToEuroConverter{
    fun convertCurrency(isoCode: String){
        console.log("Euro currency")
    }
}

 

Here our adapter

class CurrencyConverterAdapter(private val currencyToEuroConverter: CurrencyToEuroConverter): CurrencyConverter{
    override fun convertCurrency(countryName: String) {
        val currencyIsoCode = getCurrencyIsoCode()
        currencyToEuroConverter.convertCurrency(currencyIsoCode)
    }

    private fun getCurrencyIsoCode(): String = "This is ISO code"
}

fun main() {
    val currencyToDollarConverter = CurrencyToDollarConverter()
    currencyToDollarConverter.convertCurrency("Country name")

    val currencyToEuroConverter = CurrencyToEuroConverter()
    val currencyConverterAdapter = CurrencyConverterAdapter(currencyToEuroConverter)
    currencyConverterAdapter.convertCurrency("Country name")
}

 

The output will be as follows:

Dollar Currency
Euro currency

 

The adapter is very useful when you need to use incompatible third party and it is hard to change its code to match the rest, and don’t forget to take advantage of object adapter when you need to use several subclasses by adapting the interface of their parent class.

That’s all for Adapter pattern and as usual you can refer to Design patterns book by The Gang of four to get more details.

Leave a Reply

Your email address will not be published. Required fields are marked *