Design Pattern #4: Factory Pattern

Example problem: pizza shop

  // this code will need to change if pizza types change in the future
  // i.e not "closed for modification"

  def orderPizza(pizzaType:String):Pizza = { // Pizza is a trait or abstract class
    val pizza = pizzaType match {
      case "cheese" => new CheesePizza
      case "greek" => new GreekPizza
      case "pepperoni" => new PepperoniPizza
    }

    pizza.prepare()
    pizza.bake()
    pizza.cut()
    pizza.box()

    pizza

"Simple Factory Pattern"

  • Use an object to handle object creation.
  • One implementation with no subclassing.
class SimplePizzaFactory{

  def createPizza(pizzaType:String):Pizza =
    pizzaType match {
      case "cheese" => new CheesePizza
      case "greek" => new GreekPizza
      case "pepperoni" => new PepperoniPizza
    }
}

// we pass factory in constructor
class PizzaShop(factory: SimplePizzaFactory) {

  def orderPizza(pizzaType:String):Pizza = {

    val pizza = factory.createPizza(pizzaType)

    pizza.prepare()
    pizza.bake()
    pizza.cut()
    pizza.box()

    pizza

  }
}

The Good:

  • Object creation is now encapsulated.
  • Multiple instances can share one same factory.

What if we need multiple factories?

val nyPizzaFactory = new NYPizzaFactory // extends SimplePizzaFactory
val nyPizzaStore = new PizzaShop(nyPizzaFactory)

val chicagoPizzaFactory = new ChicagoPizzaFactory
val chicagoPizzaStore = new PizzaShop(chicagoPizzaStore)

hmm...

Factory Method Pattern

/**
 * Created by jsun on 11/10/2015 AD.
 */

abstract class Pizza{
  def prepare:Unit
  def bake:Unit
  def cut:Unit
  def box:Unit
}

// we pass factory in constructor
abstract class PizzaShop {

  def createPizza(pizzaType:String):Pizza // subclass must implement this "factory method"

  def orderPizza(pizzaType:String):Pizza = {

    val pizza = createPizza(pizzaType)

    // the rest procedures are localized and will be consistent

    pizza.prepare
    pizza.bake
    pizza.cut
    pizza.box

    pizza

  }
}


class NYPizzaShop extends PizzaShop{
  def createPizza(pizzaType:String) = new NYPizza
}
  • Defer object creation to subclass
  • Can localize other procedures (eg. prepare, bake, cut, box)
  • Creator class (PizzaShop) + Product class (Pizza). "Parallel classes"
  • However, creating one product only.

Now what if you have different families of pizza ingredients?

Abstract Factory Pattern

// "abstract factory"
abstract class PizzaIngredientFactory{
  def createDough:Dough // "Factory Methods"
  def createSauce:Sauce
  // will need to change interface if new ingredients are being added
}

// concrete factory
class NYIngredientFactory extends PizzaIngredientFactory{
  def createDough = new ThinCrustDough
  def createSauce = new MarinaraSauce
}


abstract class Pizza{
  var dough:Dough
  var sauce:Sauce

  def prepare:Unit
  def bake:Unit
  def cut:Unit
  def box:Unit
}

// use concrete factories through composition
class CheesePizza(ingredientFactory: PizzaIngredientFactory) extends Pizza{

  dough = ingredientFactory.createDough
  sauce = ingredientFactory.createSauce

}

class NYPizzaShop extends PizzaShop{

  def createPizza(pizzaType:String) = {
    val ingredientFactory = new NYIngredientFactory
    pizzaType match {
      case "cheese" => new CheesePizza(ingredientFactory)
    }
  }
}
  • For multiple families of object
  • Definition: create families of related or dependent objects (pizza ingredients here) without specifying their concrete classes.

Design Principle (Dependency Inversion Principle)

  • Depend upon abstractions. Do not depend upon concrete classes.

  • No variables should hold a reference to a concrete class. (eg. using new)

  • No class should derive from a concrete class.

  • No method should override an implemented method of any of its base classes.

Reference

Comments

Comments powered by Disqus