javadesign-patternsdependency-injectionfactory-patternabstract-factory

object dependency on to concrete classes


I'm trying to understand the abstract factory pattern while it's really hard. I've seen the following example, from Head First Design Patterns book, trying to describe dependencies and why being dependent is not good. However, I don't understand the following saying over that code example.

Because any change to the concrete implementations of pizzas affects the DependentPizzaStore, we say that the DependentPizzaStore “depends on” the pizza implementations.

I don't really understand how it affects that class which just initiates by news and uses bake, cut etc. methods. DependentPizzaStore doesn't know anything about the concrete implementations.

public class DependentPizzaStore {
  public Pizza createPizza(String style, String type) {
    Pizza pizza = null;
    if (style.equals("NY")) {
      if (type.equals("cheese")) {
        pizza = new NYStyleCheesePizza();
      } else if (type.equals("veggie")) {
        pizza = new NYStyleVeggiePizza();
      } else if (type.equals("clam")) {
        pizza = new NYStyleClamPizza();
      } else if (type.equals("pepperoni")) {
        pizza = new NYStylePepperoniPizza();
      }
    } else if (style.equals("Chicago")) {
      if (type.equals("cheese")) {
        pizza = new ChicagoStyleCheesePizza();
      } else if (type.equals("veggie")) {
        pizza = new ChicagoStyleVeggiePizza();
      } else if (type.equals("clam")) {
        pizza = new ChicagoStyleClamPizza();
      } else if (type.equals("pepperoni")) {
        pizza = new ChicagoStylePepperoniPizza();
      }
    } else {
      System.out.println("Error: invalid type of pizza");
      return null;
    }
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
  }
}

Solution

  • DependentPizzaStore would need to explicitly know about all the implementations in order to be able to initialize them. Therefore DependentPizzaStore is dependent and tight coupled to those implementations. If any of those implementations' constructors were to change then you would need to also change the DependentPizzaStore

    The Abstract Factory will allow you to provide an interface for creating families of related or dependent objects without specifying their concrete classes.

    Because your example also has different styles of pizza

    public interface PizzaFactory {
        string getStyle();
        Pizza createPizza(String type);
    }
    

    then there is a need for different factories for the differing styles.

    public class NYStylePizzaFactory implements PizzaFactory {
        
        public string getStyle() { return "NY"; }
        
        public Pizza createPizza(String type) {
            Pizza pizza = null;
            if (type.equals("cheese")) {
                pizza = new NYStyleCheesePizza();
            } else if (type.equals("veggie")) {
                pizza = new NYStyleVeggiePizza();
            } else if (type.equals("clam")) {
                pizza = new NYStyleClamPizza();
            } else if (type.equals("pepperoni")) {
                pizza = new NYStylePepperoniPizza();
            }
            
            if(pizza == null){
                //...throw?
            }
            
            return pizza;
        }
    }
    
    public class ChicagoStylePizzaFactory implements PizzaFactory {
        
        public string getStyle() { return "Chicago"; }
        
        public Pizza createPizza(String type) {
            if (type.equals("cheese")) {
                pizza = new ChicagoStyleCheesePizza();
            } else if (type.equals("veggie")) {
                pizza = new ChicagoStyleVeggiePizza();
            } else if (type.equals("clam")) {
                pizza = new ChicagoStyleClamPizza();
            } else if (type.equals("pepperoni")) {
                pizza = new ChicagoStylePepperoniPizza();
            }
            
            if(pizza == null){
                //...throw?
            }
            
            return pizza;
        }
    }
    

    The DependentPizzaStore can now delegate the task of creating the pizza to the factories without having to know anything about implementation details.

    public class DependentPizzaStore {
        List<PizzaFactory> factories;
        
        public DependentPizzaStore(List<PizzaFactory> factories) {
            this.factories = factories;
        }
    
        public Pizza createPizza(String style, String type) {
            Pizza pizza = null;
            for (PizzaFactory factory : factories) {
                if (factory.getStyle().equals(style)) {
                    pizza = factory.createPizza(type);
                    break;
                }
            }
            if (pizza == null){
              System.out.println("Error: invalid type of pizza");
              return null;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
            return pizza;
        }
    }
    

    Any additional styles wont affect or cause change to the store. Any changes to how the factory creates pizza also wont affect or cause change to the store.

    The store thus depends on abstractions and not on concretions.