Thursday, October 15, 2015

The Adapter Design pattern

I was reading a post from Martin Fowler titled “RequiredInterface” a couple of days ago. In his post he talks about cases, where client applications define a required interface, to which potential suppliers have to adjust in order to interact with them. Later in the article, he discusses scenarios where two components were developed independently and how the adapter pattern can be used for them to work together. So I thought, why not create a short tutorial on the adapter pattern!

The Adapter design pattern is of the structural family and allows two incompatible interfaces to work together without changing their internal structure. It is used frequently with existing code, where the necessary functionality exists but the interfaces cannot work together. The adapter pattern wraps the interface and provides another one which is compatible. It is easy to imagine how the pattern works, if we take as a metaphor the electrical outlet plugs and how adapters can be used to connect our electrical devices  when traveling to countries which use different outlet types from our own. 

To provide an example of the adapter pattern implementation, we will use a randomizer and its potential client. A randomizer is a robot which takes as input a number and it returns this specific amount of random numbers in the range of 1-10. A client wishes to receive such numbers from the randomizer and print them. However, the client requires to receive the numbers in the form of a list. A randomizer robot is available, but since it is a bit old, it returns the random numbers in an array. Adapter pattern to the rescue! 

Admittedly this example is a bit naive and “stretched” but I believe it delivers the message on the motivations and implementation approach of the adapter design pattern.


Let's start by the client of our application:
package com.tasosmartidis.design_patterns_tutorial.adapter;

public class RandomizerClient {

    RandomizerRequiredAbstract requiredRandomizer;
    
    public RandomizerClient(RandomizerRequiredAbstract requiredRandomizer) {
        this.requiredRandomizer = requiredRandomizer;
    }
    
    // Print out the number of number of random digits indicated
    public void printRandomNumbers(int number) {
        System.out.println(requiredRandomizer.generateRandomNumbers(number));
    }
}

The RandomizerRequiredAbstract, is an abstract class with a single abstract method signature, to make sure that the client receives the services in the way it requires. The source code of this class is:
package com.tasosmartidis.design_patterns_tutorial.adapter;

import java.util.List;

public abstract class RandomizerRequiredAbstract {

    // The method signature as required by the client
    public abstract List<Integer> generateRandomNumbers(int number);
}

Now let's see our old randomizer which provides the functionality but not in the format required by the client:
package com.tasosmartidis.design_patterns_tutorial.adapter;

import java.util.Random;

public class Randomizer {

    // Our randomizer receives as input the number of random digits to generate, and returns them as an array.
    public Integer[] generateRandomNumbers(int number) {
        Integer[] randomNumbers= new Integer[number];
        
        if(number<1 || number >10000)
            throw new RuntimeException("you need to provide input in range 1-10000");
        else {
            for(int i=0; i<number;i++){
                randomNumbers[i] = new Random().nextInt(10);
            }
            
            return randomNumbers;
        }           
    }
}

The adapter class will use the functionality of the old randomizer and will adjust to make it compatible with the requirements of the client:
package com.tasosmartidis.design_patterns_tutorial.adapter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class RandomizerAdapter extends RandomizerRequiredAbstract {

    Randomizer randomizer;
    
    public RandomizerAdapter(Randomizer newRandomizer) {
        this.randomizer = newRandomizer;
    }
    
    // Our randomizer adapter, uses the old randomizer that returns random 
   // numbers in an array and adapts its output to a list, so
  // that it realizes the client's requirements
    @Override
    public List<Integer> generateRandomNumbers(int number) {
        return new ArrayList<Integer>(Arrays.asList(randomizer.generateRandomNumbers(number)));
    }
}

And now we are ready to run our application and see how the client gets its functionality from the old randomizer through the adapter:
package com.tasosmartidis.design_patterns_tutorial.adapter;

public class AdapterDemo {

    public static void main(String[] args) {
        
        // The old randomizer 
        Randomizer oldRandomizer = new Randomizer();
        
        // The adapter for the randomizer
        RandomizerAdapter adapterRandomizer = new RandomizerAdapter(oldRandomizer);
        
        // Our client that needs the randomizer services
        RandomizerClient client =  new RandomizerClient(adapterRandomizer);
        
        // Print the random numbers indicated
        client.printRandomNumbers(8);       
    }
}

Running the application would just print the list of 8 random numbers as requested in the source code above. The project is available in my GitHub. This concludes our brief tutorial on the adapter design pattern. Stay tuned and more to come!



No comments:

Post a Comment