Monday, June 23, 2014

Abstract Factory Pattern

Abstract Factory Pattern: Provides an interface for creating families of
related or dependent objects without specifying their concrete
classes.
.
Often the abstract factory is mistaken for a factory method. While they are both good at decoupling applications form specific implementations, it is done in different ways. Both patterns create objects, but the factory method (factory method pattern) does it through inheritance, while the abstract factory uses object composition.

To create an object using the factory method pattern, you need to extend a class and override a factory method - which will create a concrete object. The abstract factory pattern provides an abstract type for creating a family of products (groups together a set of related products). Subclasses of this type define how those products are produced. Often you can use the factory method pattern to program an abstract factory: concrete factories implement a factory method to create their products (purely to create products).


abstract factory pattern uml
(abstract factory uml)
Using our pizza store from my previous post as an example (factory method pattern), this is how it could be implemented using the abstract factory pattern:

public class AustrianPizzaStore extends PizzaStore{
  protected Pizza createPizza(String type){
    Pizza pizza = null;
    // this is our abstract factory
    PizzaIngredientFactory ingredientFactory = new AustrianPizzaIngredientFactory();
    if(type.equals("cheese")){
      // the concrete factory is passed as argument when creating a new pizza
      pizza = new CheesePizza(ingredientFactory); 
      pizza.setName("Austrian Style Cheese Pizza");
    }else if(type.equals("veggie"){
      pizza = new VeggiePizza(ingredientFactory);
      pizza.setName("Austrian Style Veggie Pizza");
    }
    return pizza;
  }
} 

public class CheesePizza extends Pizza{
  PizzaIngredientFactory ingredientFactory;

  public CheesePizza(PizzaIngredientFactory ingredientFactory){
    this.ingredientFactory = ingredientFactory;
  }

  // the abstract pizza classhas membervariables for all
  // ingredients (e.g., Cheese, Sauce, ...)
  void prepare(){
    dough = ingredientFactory.createDough();
    sauce = ingredientFactory.createSauce();
    cheese = ingredientFactory.createCheese();
  }
}

public class AustrianPizzaIngredientFactory implements PizzaIngredientFactory {
  public Dough createDough(){
    return new ThinCrustDough();
  }
  public Sauce createSauce(){
    return new TomatoSauce();
  }
  public Cheese createCheese(){
    return new GudaCheese();
  }
  // this class has to implement every method defined in the 
  // PizzaIngredientFactory interface (Veggies, Pepperoni, Clams ...)
}
As you can see, to use the factory, one subclass is instantiated and passed into some code (AustrianPizzaFactory, CheesePizza) that is written against the abstract type. A disadvantage is that if a set of related products is extended (e.g., adding another ingredient) the whole interface has to change, which means that you have to change the interface of every subclass ... lots of work.

But the PizzaIngredientFactory allows us to handle groups of different ingredient objects. As you can imagine Austria will use different ingredients than lets say Russia or America for their cheese pizzas due to different local availability of those ingredients, or regional preferences of the population. Even though our pizza stores are using the same brand name, Austrian stores might have to use thin crust dough, gouda cheese and frozen fish, while American stores can use thick crust dough, mozzarella cheese and fresh fish.

So keep in mind to use the abstract factory whenever you have families of products that you need to create and you want to make sure that your clients create products that belong together. Use factory methods to decouple client code form concrete classes you need to instantiate, or if you don't know ahead of time which concrete classes you are going to need.

No comments:

Post a Comment