Adapter Pattern

Definition

The Adapter Pattern converts the interface of a class into another interface the client expects. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.

image

Figure 01. Class diagram Adapter Pattern. Ref: Heads First Design Patterns

Scenario

The inventory system of a store displays all the products that are in stock and the system is working perfectly fine.

A new requirement comes to extend the functionality of the inventory system to display all the products of other store that was just acquired and needs to be integrated to our inventory system. This other store also have Product objects though the properties of them is different than the Products that the current inventory system use.

To integrate the Product objects of the other store to our Inventory system we will apply the Adapter Pattern as shown in the next figure:

AdapterPattern (1)

Figure 02. Class diagram of the solution proposed.

As can be seen in the class diagram of the previous figure, the class ProductAdapter that extends the interface IProduct, will adapt the product objects of the other store (ProductStoreX), to the IProduct types that are expected by the Client (Main class), in the following way:

ProductStoreX,java 

package chris.desingpatterns.adapter.products; import chris.desingpatterns.adapter.productsadapter.ProductStoreX; /** * Adapter that will adapt products from * another store so they can be displayed * with the same logic of the products of * the current store. * * @author croman * */ public class ProductStoreXAdapter implements IProduct { private ProductStoreX product; public ProductStoreXAdapter(ProductStoreX product) { this.product = product; } public String getName() { return product.getProductName(); } public String getId() { return product.getProductSerialNumber(); } /** * The price of a product in the other store is returned * as a String, but the products of the current store are * primitives type double, this method will convert the * string to type double. */ public double getPrice() { Double price = Double.parseDouble(product.getPrice()); return price; } /** * The products of the other store are categorized by different groups * than the ones that are used in the prodcuts of the current system, * for this reason, this method will convert the groups of the products * of the other store to ProductCategory enums. * */ public ProductCategory getCategory() { //Groups: TV, RADIO, TELEPHONE, LAPTOP, APPLIANCES String group = product.getProductGroup(); ProductCategory category; if(group.equals("RADIO")){ category = ProductCategory.HIFI; } else if(group.equals("TELEPHONE")){ category = ProductCategory.MOBILE; } else if(group.equals("LAPTOP")){ category = ProductCategory.COMPUTER; } else { category = ProductCategory.GENERAL; } return category; } }

As can be seen in the previous snippet, all the methods that are not equivalent to the methods expected by the Client class, as getCategory() and getPrice(), have an extra logic that will make them alike.

The interface that has all the methods that will be used by the Client is:

IProduct.java

package chris.desingpatterns.adapter.products; /** * Generic interface that will expose * all the information that need to * be displayed in a user interface. * * @author croman * */ public interface IProduct { String getName(); String getId(); double getPrice(); ProductCategory getCategory(); }

And the client that will, in this case, display all the products in stock, is:

Main.java

package chris.desingpatterns.adapter; import java.util.ArrayList; import java.util.List; import chris.desingpatterns.adapter.products.IProduct; import chris.desingpatterns.adapter.products.ProductStoreXAdapter; import chris.desingpatterns.adapter.products.ProductsGenerator; import chris.desingpatterns.adapter.productsadapter.ProductStoreX; import chris.desingpatterns.adapter.productsadapter.ProductStoreXGenerator; public class Main { public static void main(String ... args){ List<IProduct> products = ProductsGenerator.getAllProducts(); System.out.println("Products from the store:"); productDisplayer(products); // Adapt the products from the other store to products // of this store. List<ProductStoreX> productsStoreX = ProductStoreXGenerator.getAllProducts(); List<IProduct> productsAdapted = adaptProdustsStoreXToProducts(productsStoreX); products.addAll(productsAdapted); System.out.println("Products from the store X + products from our store:"); productDisplayer(products); } /** * Method that will adapt product types ProductStoreX * to IProduct. * * @param productsStoreX * @return */ private static List<IProduct> adaptProdustsStoreXToProducts(List<ProductStoreX> productsStoreX){ List<IProduct> products = new ArrayList<IProduct>(); for(ProductStoreX productX : productsStoreX){ ProductStoreXAdapter productAdapted = new ProductStoreXAdapter(productX); products.add(productAdapted); } return products; } /** * Method that will print the information of all the products * that are present in the store. * * @param products */ private static void productDisplayer(List<IProduct> products){ List<IProduct> generalProducts = new ArrayList<IProduct>(); List<IProduct> computerProducts = new ArrayList<IProduct>(); List<IProduct> hifiProducts = new ArrayList<IProduct>(); List<IProduct> mobileProducts = new ArrayList<IProduct>(); for(IProduct product : products){ switch(product.getCategory()){ case COMPUTER: computerProducts.add(product); break; case HIFI: hifiProducts.add(product); break; case MOBILE: mobileProducts.add(product); break; default: generalProducts.add(product); } } System.out.println("********************************"); System.out.println("********************************"); System.out.println("PRODUCTS IN STORE:"); System.out.println("Computers:"); for(IProduct p : computerProducts){ printProductInfo(p); } System.out.println("Mobile Phones:"); for(IProduct p : mobileProducts){ printProductInfo(p); } System.out.println("HIFI Products:"); for(IProduct p : hifiProducts){ printProductInfo(p); } System.out.println("Products in General:"); for(IProduct p : generalProducts){ printProductInfo(p); } System.out.println("********************************"); System.out.println("********************************"); } private static void printProductInfo(IProduct p){ System.out.println(" + " + p.getName() + " ["+p.getId()+"] : " + p.getPrice() + ""); } }

As can be seen in the Main class, both Product objects (Product and ProductStoreX) can be threated the same way, without changing the logic to print the information about them.

That is it!

To download the code click in the following link:

Adapter Pattern

Do you have any recommendations or amendments to be done, please let me know to improve this post!

DukeNY

Decorator Pattern

Definition

The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

image

Figure 01. Decorator Pattern class diagram. Ref: http://en.wikipedia.org/wiki/Decorator_pattern

Scenario

Crazy Phones (a mobile phone company provider) wants a web shop on which its customers can choose any of all the offers that they have on mobile services. For now they have the following offers:

  • SIM only plans, where customers can make and receive calls.
  • SMS plans, where customers can send and receive sms’s.
  • Internet, where customers can also have internet in their phones.

One of the main requirements is that these plans can be combined, something like: SIM + SMS, SIM + Internet, etc, being SIM only the mandatory basic plan (no SIM no calls, no sms’s, no internet… makes sense Smile with tongue out)
Another important requirement is that the plans can change from time to time, so the design has to be flexible to changes.

For this scenario, we could propose the following solution:

image

Figure 02. Class diagram of the solution proposed for this scenario.

As it can be seen in this diagram we will apply the Decorator Pattern, because this pattern fits perfectly well on the requirements specified.

  1. Easy to extend to different types of plans (SimSms500, Sim1000, Internet 2GB, etc)
  2. The MOST IMPORTANT, the basic plan (SimXXX) can be decorated with other plans on it.

This can be better understood checking the code implemented.

Lets first check the SimPackageInterface:

SimPackageInterface.java

package chris.desingpatterns.decorator.sim;

public interface SimPackageInterface {
    
    /**
     * Method that will return the name of the
     * SIM Package plan.
     * @return
     */
    String getName();
    
    /**
     * Method that will return the description
     * of the package plan.
     * 
     * @return
     */
    String getDescription();
    
    /**
     * Method that will return the cost
     * for the SIM Plan.
     * @return
     */
    double getCost();

}

This interface is the one that expose all the required methods for a SIM plan object.

The decorator looks like this:

SimPackageDecorator.java

package chris.desingpatterns.decorator.sim.decorators;

import chris.desingpatterns.decorator.sim.SimPackageInterface;

public abstract class SimPackageDecorator implements SimPackageInterface {
    private SimPackageInterface simPackage;
    
    /**
     * With this constructor we will force all the 
     * classes that extend it, to set a Sim Package
     * class to decorate it.
     * 
     * @param simPackage
     */
    public SimPackageDecorator(SimPackageInterface simPackage){
        this.simPackage = simPackage;
    }
    
    public SimPackageInterface getSimPackage() {
        return simPackage;
    }

}

The Decorator (as the pattern describes it) will need the class that needs to be decorated (in this case any class that implements the SimPackageInterface)

One of the classes that extend the decorator is the SimInternet500 class, this class will Decorate any class that extends the SimPackageInterface to add an extra Internet service to the SIM plan.

SimInternet500.java

package chris.desingpatterns.decorator.sim.decorators.internet;

import chris.desingpatterns.decorator.sim.SimPackageInterface;
import chris.desingpatterns.decorator.sim.decorators.SimPackageDecorator;

public class SimInternet500 extends SimPackageDecorator {

    public SimInternet500(SimPackageInterface simPackage) {
        super(simPackage);
    }
    
    /**
     * {@inheritDoc}
     */
    public String getName() {
        return getSimPackage().getName() + " + Internet 500";
    }
    
    /**
     * {@inheritDoc}
     */
    public String getDescription() {
        return getSimPackage().getDescription() + 
                " + 500 MB of internet at max 2 Mbps";
    }
    
    /**
     * {@inheritDoc}
     */
    public double getCost() {
        return getSimPackage().getCost() + 10.00;
    }

}  

This class attach additional information (methods: getName() and getDescritpion()) and additional functionality (creating an extra calculation of prices in getCost()) thanks to the use of the Decorator pattern without changing or extending the classes that directly extend the interface SimPackageInterface.

The class that is running this scenario is:

Main.java

package chris.desingpatterns.decorator;

import chris.desingpatterns.decorator.sim.Sim100;
import chris.desingpatterns.decorator.sim.Sim200;
import chris.desingpatterns.decorator.sim.SimPackageInterface;
import chris.desingpatterns.decorator.sim.SimUnlimited;
import chris.desingpatterns.decorator.sim.decorators.internet.SimInternet500;
import chris.desingpatterns.decorator.sim.decorators.internet.SimInternetUnlimited;
import chris.desingpatterns.decorator.sim.decorators.sms.SimSmsUnlimited;

public class Main {

    /**
     * In this example we will have three customers
     * 
     * - Customer A just needs to do calls all the time (SIM Unlimited)
     * - Customer B order a SIM for calling (SIM 200) + 
     *   Unlimited SMS's + Internet 500
     * - Customer C wants more Internet than anything else Unlimited 
     *      Internet, so he choose the basic calls plan (SIM 100). 
     *   He does not need SMS's because he will use Internet to send SMS's
     *    
     * @param args
     */
    public static void main(String[] args) {
        
        //Customer A
        Customer customerA = new Customer("Christian");
        SimPackageInterface simUnlimited = new SimUnlimited();
        customerA.setSimPackage(simUnlimited);
        printInfo(customerA);
        
        //Customer B
        Customer customerB = new Customer("Alejandro");
        SimPackageInterface sim200 = new Sim200();
        sim200 = new SimSmsUnlimited(sim200);
        sim200 = new SimInternet500(sim200);
        customerB.setSimPackage(sim200);
        printInfo(customerB);
        
        //Customer C
        Customer customerC = new Customer("Marcelo");
        SimPackageInterface simInternet = new Sim100();
        simInternet = new SimInternetUnlimited(simInternet);
        customerC.setSimPackage(simInternet);
        printInfo(customerC);
        
    }
    
    /**
     * Method that will print the customer info
     * + the SIM package that he chose.
     * 
     * @param customer
     */
    private static void printInfo(Customer customer) {
        SimPackageInterface simPackage = customer.getSimPackage();
        System.out.println("=========================================");
        System.out.println(customer.getName() + " ordered: ");
        if(simPackage != null){
            System.out.println("- Name of the package: " + 
                                simPackage.getName());
            System.out.println("- The description: " + 
                                simPackage.getDescription());
            System.out.println("- The cost: " + 
                                simPackage.getCost() + " Euros");
        }
        System.out.println("=========================================");
        
    }

}

That is it!
Starting a new mobile phone contract or renewing an existing one usually will give you these options to select the contract that best fits your necessities… so there we have here an application of the Decorator Pattern to fulfill these requirements.

Do you want to run the code by yourself? Then download the source code here:

Decorator Pattern

Do you have any recommendations or amendments to be done, please let me know to improve this post!