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.
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","[email protected]"); customers.add(customerFromDB); CustomerFromLegacyCode customerFromLegacyCode = new CustomerFromLegacyCode( "777", "Java", "Juggy", "[email protected]"); customers.add(new CustomerAdapterFromLegacyCode(customerFromLegacyCode)); CustomerCSV customerCSV = new CustomerCSV("567,James,Gosling,[email protected]"); 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; public LegacyProductPOJO buildDefault() { setA01("CustomerId"); setA02("CustomerName"); setA02("CustomerLastName"); setA03("CustomerEmail"); return this; } // 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).
Can’t see this method anywhere: new LegacyProductPOJO().buildDefault()
What should it do?
Hi Pavel, thanks for the feedback! I just inserted the buildDefault method in the article. But, remember you can also download the source-code of all the patterns. Keep the code on!
Thank you for your reply! I know there is code to download, but I always like to go step by step adding the code, because I understand the idea of the pattern better this way.
Yes Pavel. Well, thank you for the heads up and stay tuned for more content!
Keep the code on!