Design pattern : adapter implementation in Java

The source code of design patterns can be viewed and downloaded here :
https://github.com/ebundy/java-examples/tree/master/design-patterns

What is it ?

The design pattern adapter is a structural pattern.
It allows to adapt the API of an existing class (the adapted class) to the API of another class (the adapter class).

When using it ?

– You want to use an existing class but its interface doesn’t suit for your needs.
– You want to use an existing class but you want to add to it new operations.
– You want to use multiple existing classes but creating a subclass for each existing class is not convenient.

This patterns has two forms : inheritance and composition forms.
The composition form addresses all usages whereas the inheriting form cannot address the adaptation multiple existing classes.

We must not generalize but in the facts, the composition is more powerful because it provides more flexibility and it leaves the choice of the API (in terms of contract) of the adapter class.
Besides, inheritance in Java is not always naturally allowed and foreseen by classes.
For example, if the class is declared final or that methods we wish override are declared final, we are blocked. Other example, if the class we want to subclass owns private fields without public accessors for them and we need to use these fields in our subclass, we are still annoyed.
Of course, we can bypass this issue by using reflection but in this case, we introduce bad design practices since we break the object encapsulation.
Composition is nevertheless not always the best choice.
Indeed, composition is irrelevant if we need to override methods of the adapted objects.
It is also the case when we create a adapter for adapting a class owning many publics methods and that the adapter needs all methods of the adapted.
In these cases, inheritance looks like a more natural choice.

We will study and implement two use cases : one where composition appears like the most relevant choice and the other where inheriting appears like the most relevant choice.
It allows us to show how according to the use case, a solution is more suitable than another.

Our use case for an adapter with composition : a zoo modeling

We want to model a zoo and of course and there is no zoo without a set of animals.
Our need for the zoo is the following :
– we can add an animal.
we can free an animal if the animal is freeable.
we can feed herbivores.
we can find all animals in.
– Zoo class operation names must be specific and domain specific.

Java proposes multiple ways of representing a set of objects. For example, the ArrayList could be a good candidate to represent our zoo but deriving from it is not suitable.
Why ?
– We want a Zoo interface with relevant method names in order to ease the use of the Zoo clas. ArrayList is not domain specific.
– The ArrayList API is not enough because it lacks some methods. For example, we need to feed herbivores of our zoo.
– We don’t want zoo clients to use directly the ArrayList API otherwise they could bypass rules. For example, providing to zoo clients a class inheriting from ArrayList would allow them to remove an element of the list with the remove() method and it is not desirable since we wish to do some checks to validate a animal removal.

Our need encourages a composition implementation because the zoo contract, the need for data integrity and rules compliance demands that the ArrayList be wrapped and not directly available for clients of the Zoo class.
Let’s go with an adapter relying on composition.

adapter-zoo-composition

Common classes

Animal

package davidhxxx.teach.designpattern.adapter;
 
public abstract class Animal {
 
    public abstract boolean isHerbivore();
 
    private int hungryLevel;
 
    public void feed() {
	if (hungryLevel > 0) {
	    hungryLevel--;
	}
    }
 
    public boolean isFreeable() {
	return false;
    }
 
}

Zebra

package davidhxxx.teach.designpattern.adapter;
 
public class Zebra extends Animal {
 
    @Override
    public boolean isHerbivore() {
	return true;
    }
 
}

Lion

package davidhxxx.teach.designpattern.adapter;
 
public class Lion extends Animal {
 
    @Override
    public boolean isHerbivore() {
	return false;
    }
 
}

Zoo

package davidhxxx.teach.designpattern.adapter;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
public class ZooWithComposition{
 
    private ArrayList innerList = new ArrayList();
 
    public boolean addAnimal(Animal animal) {
	return innerList.add(animal);
    }
 
    public boolean freeAnimal(Animal animal) {
	if (animal.isFreeable()) {
	    return innerList.remove(animal);
	}
	return false;
    }
 
    public boolean feedHerbivores() {
	boolean isAtLeastOneAnimalFed = false;
	for (Animal currentAnimal : innerList) {
	    if (currentAnimal.isHerbivore()) {
		currentAnimal.feed();
		isAtLeastOneAnimalFed = true;
	    }
	}
 
	return isAtLeastOneAnimalFed;
    }
 
    public List findAnimals() {
	return Collections.unmodifiableList(innerList);
    }
 
}

Unit Test (doesn’t cover all methods, just to show an usage example)

package davidhxxx.teach.designpattern.adapter.zoo.composition;
 
import java.util.List;
 
import org.junit.Before;
import org.junit.Test;
 
import davidhxxx.teach.designpattern.adapter.zoo.common.Animal;
import davidhxxx.teach.designpattern.adapter.zoo.common.Zebra;
import davidhxxx.teach.designpattern.adapter.zoo.composition.ZooWithComposition;
import junit.framework.Assert;
 
public class ZooWithCompositionTest {
 
    private ZooWithComposition zoo;
 
    @Before
    public void setup() {
	zoo = new ZooWithComposition();
    }
 
    @Test
    public void addAnimal_adds_an_animal_retrievable_with_find_animals_but_is_not_modifiable() throws Exception {
	final Zebra animalAdded = new Zebra();
 
	// action
	zoo.addAnimal(animalAdded);
	// assertion
	List animals = zoo.findAnimals();
	Assert.assertEquals(1, animals.size());
	Assert.assertSame(animalAdded, animals.get(0));
 
	boolean isExceptionWhenAdded = false;
	try {
	    animals.remove(0);
	}
	catch (UnsupportedOperationException e) {
	    isExceptionWhenAdded = true;
	}
	Assert.assertTrue("exception expected when adding", isExceptionWhenAdded);
    }
 
 
}

Now, we will check by the practice that an adapter based on a inheritance strategy is not appropriate for our zoo use case.

Version with inheritance of the adapted class
(not finished because too complicated)

This version is much less convenient because our need (integrity data and removal rule) for the zoo forces us to protect many methods.

package davidhxxx.teach.designpattern.adapter.inheriting;
 
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
 
import davidhxxx.teach.designpattern.adapter.common.Animal;
 
@SuppressWarnings("serial")
public class ZooWithInheritance extends ArrayList {
 
    public boolean addAnimal(Animal animal) {
	return super.add(animal);
    }
 
    public boolean freeAnimal(Animal animal) {
	if (animal.isFreeable()) {
	    return super.remove(animal);
	}
	return false;
    }
 
    /**
     * we cannot delete with bypassing rules
     */
    @Override
    public boolean remove(Object o) {
	throw new UnsupportedOperationException();
    }
 
    /**
     * we cannot delete with bypassing rules
     */
    @Override
    public Animal remove(int i) {
	throw new UnsupportedOperationException();
    }
 
    /**
     * we cannot delete with bypassing rules
     */
    @Override
    public boolean removeAll(Collection<?> c) {
	throw new UnsupportedOperationException();
    }
 
    public boolean feedHerbivores() {
	boolean isAtLeastOneAnimalFed = false;
	for (Animal currentAnimal : this) {
	    if (currentAnimal.isHerbivore()) {
		currentAnimal.feed();
		isAtLeastOneAnimalFed = true;
	    }
	}
 
	return isAtLeastOneAnimalFed;
    }
 
    public List findAnimals() {
	return Collections.unmodifiableList(this);
    }
 
}

Unit test

The test shows that even if our zoo with ArrayList inheritance provides operations to operate on zoo and protect to a certain degree the zoo rules, finally, this solution brings too much complexity.
For example, I have forgotten to protect the ArrayList’s clear() method. So we can free all animals without checking the removal rule in the test zooInterfaceIsTooPermissive().
But even if all methods to protect were protected, would it be an acceptable solution ?
Not really because it is misleading for a client to get a class with an API which most operations are unsupported at runtime. Eventually, we get just a not practical class Zoo to use and to maintain.

package davidhxxx.teach.designpattern.adapter.zoo.inheriting;
 
import java.util.List;
 
import org.junit.Before;
import org.junit.Test;
 
import davidhxxx.teach.designpattern.adapter.zoo.common.Animal;
import davidhxxx.teach.designpattern.adapter.zoo.common.Zebra;
import davidhxxx.teach.designpattern.adapter.zoo.inheriting.ZooWithInheritance;
import junit.framework.Assert;
 
public class ZooWithInheritanceTest {
 
    private ZooWithInheritance zoo;
 
    @Before
    public void setup() {
	zoo = new ZooWithInheritance();
    }
 
    @Test
    public void addAnimal_adds_an_animal_retrievable_with_find_animals_but_is_not_modifiable() throws Exception {
	final Zebra animalAdded = new Zebra();
 
	// action
	zoo.addAnimal(animalAdded);
	// assertion
	List animals = zoo.findAnimals();
	Assert.assertEquals(1, animals.size());
	Assert.assertSame(animalAdded, animals.get(0));
 
	boolean isExceptionWhenAdded = false;
	try {
	    animals.remove(0);
	}
	catch (UnsupportedOperationException e) {
	    isExceptionWhenAdded = true;
	}
	Assert.assertTrue("exception expected when adding", isExceptionWhenAdded);
    }
 
    @Test
    public void zooInterfaceIsTooPermissive() throws Exception {
	final Zebra animalAdded = new Zebra();
 
	// action
	zoo.addAnimal(animalAdded);
	// assertion
	 zoo.clear();
	Assert.assertEquals(0, zoo.findAnimals().size());
    }
}

Our use case for an adapter with inheritance : an object mapper

We need a object mapper for our application. We know that many third-party libraries allow to do it. So, we chose one of them : ModelMapper. Our object mapper should provide all classic methods to map data from an object to another and it should also provide one custom need : a method to map a List of Integer to a List of String and reversely.

ModelMapper matches with our first requirement : « providing all classic methods to map data from an object to another ».
For the second requirement : « providing a method to map a List of integer to a List of String and reversely », as it is a custom need, ModelMapper doesn’t provide it natively but nothing prevents us from providing that feature to our application. Now, the question is how to do it ?

Using two mappers : the ModelMapper and a custom mapper doesn’t seem to be a good idea. It separates related responsibilities instead of gathering them.
Besides, it complicates the client usage of mappers in our application. When using one ? When using the other ?

So, we will use a single mapper : a custom mapper relying on ModelMapper but with some changes to match our needs.
In other words, we will still use a adapter.
All operations for ModelMapper are needed, so we don’t see why using indirections with a composed adapter.  Inheritance appears as a natural solution.

adapter-mapper-inheritance

ModelMapperWithInheritance

package davidhxxx.teach.designpattern.adapter.mapper.inheriting;
 
import java.util.ArrayList;
import java.util.List;
 
import org.modelmapper.ModelMapper;
 
public class ModelMapperWithInheritance extends ModelMapper {
 
    public List<String> mapToListOfString(List<Integer> integers) {
	List<String> list = new ArrayList<>();
	for (Integer integer : integers) {
	    list.add(String.valueOf(integer));
	}
	return list;
    }
 
    public List<Integer> mapToListOfInteger(List<String> strings) {
	List<Integer> list = new ArrayList<>();
	for (String integer : strings) {
	    list.add(Integer.valueOf(integer));
	}
	return list;
    }
 
}

ModelMapperWithInheritanceTest

package davidhxxx.teach.designpattern.adapter.mapper.inheriting;
 
import java.util.Arrays;
import java.util.List;
 
import org.junit.Before;
import org.junit.Test;
 
import junit.framework.Assert;
 
public class ModelMapperWithInheritanceTest {
 
    private ModelMapperWithInheritance modelMapper;
 
    @Before
    public void before() {
	modelMapper = new ModelMapperWithInheritance();
    }
 
    @Test
    public void mapToListOfInteger_map_strings_to_integers() throws Exception {
	List<Integer> actualIntegers = modelMapper.mapToListOfInteger(Arrays.asList("00", "15", "-15"));
	List<Integer> expectedIntegers = Arrays.asList(new Integer(0), new Integer(15), new Integer(-15));
 
	Assert.assertEquals(expectedIntegers, actualIntegers);
    }
 
    @Test
    public void mapToListOString_map_integers_to_strings() throws Exception {
	List<String> actualStrings = modelMapper.mapToListOfString(Arrays.asList(new Integer(0), new Integer(15), new Integer(-15)));
	List<String> expectedStrings = Arrays.asList("0", "15", "-15");
 
	Assert.assertEquals(expectedStrings, actualStrings);
    }
 
}
Ce contenu a été publié dans design pattern, java. Vous pouvez le mettre en favoris avec ce permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *