DESIGN PATTERNS SAGA #9: REAL PROJECT SITUATIONS WITH ADAPTER

When we can’t modify a legacy code and have to adapt the new code, it’s a good idea to use the Adapter Pattern. We can use this Pattern when we are using a legacy Service which we can’t change and the response is a really big object with unintelligible names. In the new Service, we don’t want to receive all the big object information from the legacy Service and we want to avoid using the big object directly. To do this, we can use the generic interface to create the Adapter and adapt the legacy POJO in the way we want.

adapter_diagram.PNG

Get the Design_Patterns_Saga_GitHub_Source_Code

1 – Adapter interface: In order to get any class adapted to what we need, first we must create a class or an interface that is generic so we can use polymorphism.

public interface Customer {

	public String getId();
	public String getFirstName();
	public String getLastName();
	public String getEmail();

}

2 – POJOs we want to adapt: These are the POJOs we must adapt to the Service process.

public class CustomerFromLegacyCode {

	private String cn;
	private String surname;
	private String givenName;
	private String mail;

        // Constructor, getters and setters omitted.

}

public class CustomerCSV {

	private int id;
	private String firstname;
	private String lastname;
	private String emailAddress;

	public CustomerCSV(String values) {
		StringTokenizer tokenizer = new StringTokenizer(values, ",");
		if (tokenizer.hasMoreElements()) {
			id = Integer.parseInt(tokenizer.nextToken());
		}
		if (tokenizer.hasMoreElements()) {
			firstname = tokenizer.nextToken();
		}
		if (tokenizer.hasMoreElements()) {
			lastname = tokenizer.nextToken();
		}
		if (tokenizer.hasMoreElements()) {
			emailAddress = tokenizer.nextToken();
		}
	}

        // Getters and setters omitted.

}

3 – Adapted classes: These classes are the Adapters! Basically, we implement the Adapter interface and encapsulate the POJO that must be adapted in the constructor of the Adapter. Then, we just override the Adapter interface methods and delegate the method invocation to the class to be adapted to get the correct value, and it’s done! We can use the adapters now!

public class CustomerAdapterCSV implements Customer {

	private CustomerCSV instance;

	public CustomerAdapterCSV(CustomerCSV instance) {
		this.instance = instance;
	}

	@Override
	public String getId() {
		return instance.getId() + "";
	}

	@Override
	public String getFirstName() {
		return instance.getFirstname();
	}

	@Override
	public String getLastName() {
		return instance.getLastname();
	}

	@Override
	public String getEmail() {
		return instance.getEmailAddress();
	}

}

public class CustomerAdapterFromLegacyCode implements Customer {

	private CustomerFromLegacyCode instance;

	public CustomerAdapterFromLegacyCode(
                  CustomerFromLegacyCode instance) {
		this.instance = instance;
	}

	@Override
	public String getId() {
		return instance.getCn();
	}

	@Override
	public String getFirstName() {
		return instance.getGivenName();
	}

	@Override
	public String getLastName() {
		return instance.getSurname();
	}

	@Override
	public String getEmail() {
		return instance.getMail();
	}

	public String toString() {
		return "ID: " + instance.getCn();
	}

}

public class CustomerDB implements Customer {

	private String id;
	private String firstName;
	private String lastName;
	private String email;

	public CustomerDB(String id, String firstName,
                           String lastName, String email) {
		this.id = id;
		this.firstName = firstName;
		this.lastName = lastName;
		this.email = email;
	}

	public String getId() {
		return id;
	}

	public String getFirstName() {
		return firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public String getEmail() {
		return email;
	}

	public String toString() {
		return "ID: " + id + ", First name: " + firstName + ", Last
                           name: " + lastName + ", Email: " + email;
	}

}

4 – Using the Adapter: Now that we have the Adapters ready, we can encapsulate the POJOs we want in each Adapter. We put information into the objects and also put the POJO to be adapted inside the Adapter. Then we can add in the Customer List since it is from the Customer type.

public class CustomerService {

	public List<Customer> getEmployeeList() {
		List<Customer> customers = new ArrayList<>();

		Customer customerFromDB = new CustomerDB("1234",
                         "Robert", "C. Martin","bobMartin@cleancode.com");

		customers.add(customerFromDB);

		CustomerFromLegacyCode customerFromLegacyCode =
				new CustomerFromLegacyCode(
                     "777", "Java", "Juggy", "juggy@java.com");

		customers.add(new
                     CustomerAdapterFromLegacyCode(customerFromLegacyCode));

		CustomerCSV customerCSV = new
                   CustomerCSV("567,James,Gosling,jamesGosling@java.com");

		customers.add(new CustomerAdapterCSV(customerCSV));

		return customers;
	}

}

Bonus example

1 – Adapter interface:

public interface Product {

	String getName();

	BigDecimal getPrice();

	String productType();

	void processProduct();

	boolean isProcessed();

}

2 – Legacy Service:

public class LegacyProductService {

	public LegacyProductPOJO findLegacyProduct() {
		return new LegacyProductPOJO().buildDefault();
	}

}

3 – Legacy Product POJO:

public class LegacyProductPOJO {

	private String a01;
	private String a02;
	private String a03;
	private String a04;
	private String a05;
	private String a06;
	private String a07;
	private String a08;
	private String a09;
	private String a10;
	private String a11;
	private String a12;
	private String a13;
	private String a14;
	private String a15;
	private String a16;
	private String a17;
	private String a18;
	private String a19;
	private String a20;

     // Getters and setters omitted

}

4 – Adapter class:

public class LegacyProductAdapter implements Product {

	private boolean processed;
	private LegacyProductPOJO legacyProductPOJO;

	public LegacyProductAdapter(LegacyProductPOJO legacyProductPOJO) {
		this.legacyProductPOJO = legacyProductPOJO;
	}

	@Override
	public String getName() {
		return legacyProductPOJO.getA01();
	}

	@Override
	public BigDecimal getPrice() {
		return new BigDecimal(legacyProductPOJO.getA02());
	}

	@Override
	public String productType() {
		return legacyProductPOJO.getA03();
	}

	@Override
	public void processProduct() {
		System.out.println("Processing product..");
		processed = true;
	}

	@Override
	public boolean isProcessed() {
		return processed;
	}

}

5 – New Service to be invoked:

public class ProductService {

	public void processProduct(Product product) {
		product.processProduct();
	}

}

6 – The Unit Test:

public class BonusAdapterTest {

	@Test
	public void legacyServiceAdapterTest() {
		LegacyProductPOJO legacyProduct =
                    new LegacyProductService().findLegacyProduct();

		Product adapter = new LegacyProductAdapter(legacyProduct);

		ProductService productService = new ProductService();
		productService.processProduct(adapter);

		Assert.assertTrue(adapter.isProcessed());
	}

}

Summary of actions:

1 – Created the Adapter generic interface.
2 – Created the Adapter’s classes to adapt the POJOs.
3 – Implemented the Customer interface in each Adapter class.
4 – Encapsulated the POJO inside each Adapter class.
5 – Created a constructor inside the Adapter class receiving the POJO.
6 – Overrode the Adapter interface methods using the POJO class.
7 – Instantiated the Adapter class and put the POJO inside it.

To practice the Adapter Pattern you can create another class to be adapted, for example, the XMLCustomer and also create the Adapter for this customer! Create another test method to make sure it works! Try to use TDD (Test Driven Development).

DESIGN PATTERNS SAGA #9: REAL PROJECT SITUATIONS WITH ADAPTER

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s