Javascript. Abstract Factory pattern

Паттерн Абстрактная фабрика (Abstract Factory pattern) предоставляет интерфейс для создания семейств взаимосвязанных объектов без указания их конкретных классов. Задача Абстрактной Фабрики - создание семейств взаимосвязанных объектов без зависимости от их конкретных классов. В то время как задача Фабричного Метода - перемещение создания экземпляров в субклассы. Данный паттерн вместе с паттерном Фабричный Метод инкапсулируют создание объектов, обеспечивая слабую связанность и гибкость архитектур. Данный паттерн относится к порождающим паттернам. Рассмотрим пару реализаций паттерна.


var Employee = function(name) {
    this.name = name;
    this.say = function () {
        log.add("I am employee " + name);
    };
};

var EmployeeFactory = function () {
    this.create = function(name) {
        return new Employee(name);
    };
};

var Vendor = function (name) {
    this.name = name;
    this.say = function () {
        log.add("I am vendor " + name);
    };
};

var VendorFactory = function () {
    this.create = function(name) {
        return new Vendor(name);
    };
};

// log helper
var log = (function () {
    var log = "";   
    return {
        add: function (msg) { log += msg + "\n"; },
        show: function () { console.log(log); log = ""; }
    };
})();

/* testing */

var Application = function () {
    this.run = function () {
        var persons = [],
            employeeFactory = new EmployeeFactory(),
            vendorFactory = new VendorFactory();
       
        persons.push(employeeFactory.create("Joan DiSilva"));
        persons.push(employeeFactory.create("Tim O'Neill"));
        persons.push(vendorFactory.create("Gerald Watson"));
        persons.push(vendorFactory.create("Nicole McNight"));
       
        for (var i = 0, len = persons.length; i < len; i++) {
            persons[i].say();
        }
       
        log.show();
    };
};

var app = new Application();
app.run();


На выходе

I am employee Joan DiSilva
I am employee Tim O'Neill
I am vendor Gerald Watson
I am vendor Nicole McNight


Или вот такая монструозная реализация. В данной реализации представлены фабрики пицц и входящих в них ингридиентов.

/* interface AbstractFactory */

var PizzaIngredientFactory = function () {
    this.createDough = function () {};
    this.createSauce = function () {};
    this.createCheese = function () {};
    this.createVeggies = function () {};
    this.createPepperoni = function () {};
    this.createClam = function () {};
};

/* ConcreteFactory - NYPizzaIngredientFactory */

var NYPizzaIngredientFactory = function () {
  
    this.createDough = function () {
        return new ThinCrustDough();
    };
  
    this.createSauce = function () {
        return new MarinaraSauce();
    };
  
    this.createCheese = function () {
        return new ReggianoCheese();
    };
  
    this.createVeggies = function () {
        var veggies = [
            new Garlic(),
            new Onion(),
            new Mushroom(),
            new RedPepper()
        ];
        return veggies;
    };
  
    this.createPepperoni = function () {
        return new SlicedPepperoni();
    };
  
    this.createClam = function () {
        return new FreshClams();
    };
  
};

NYPizzaIngredientFactory.prototype = new PizzaIngredientFactory();
NYPizzaIngredientFactory.prototype.constructor = NYPizzaIngredientFactory;

/* ConcreteFactory - ChicagoPizzaIngredienFactory */


var ChicagoPizzaIngredienFactory = function () {
  
    this.createDough = function () {
        return new ThinCrustDough();
    };
  
    this.createSauce = function () {
        return new PlumTomatoSauce();
    };
  
    this.createCheese = function () {
        return new MozzarellaCheese();
    };
  
    this.createVeggies = function () {
        var veggies = [
            new BlackOlives(),
            new Spinach(),
            new Eggplant()
        ];
        return veggies;
    };
  
    this.createPepperoni = function () {
        return new SlicedPepperoni();
    };
  
    this.createClam = function () {
        return new FrozenClams();
    };
  
};

ChicagoPizzaIngredienFactory.prototype = new PizzaIngredientFactory();
ChicagoPizzaIngredienFactory.prototype.constructor = ChicagoPizzaIngredienFactory;

/* AbstractProduct - interface Veggies */

var Veggies = function () {
    this.toString = function () {};
};

/* AbstractProduct - interface Cheese */

var Cheese = function () {
    this.toString = function () {};
};

/* AbstractProduct - interface Clams */

var Clams = function () {
    this.toString = function () {};
};

/* AbstractProduct - interface Dough */

var Dough = function () {
    this.toString = function () {};
};

/* AbstractProduct - interface Pepperoni */

var Pepperoni = function () {
    this.toString = function () {};
};

/* AbstractProduct - interface Sauce */

var Sauce = function () {
    this.toString = function () {};
};

/* ConcreteProduct - PlumTomatoSauce */

var PlumTomatoSauce = function () {
    this.toString = function () {
        return "Tomato sauce with plum tomatoes";
    };
};

PlumTomatoSauce.prototype = new Sauce();
PlumTomatoSauce.prototype.constructor = PlumTomatoSauce;

/* ConcreteProduct - RedPepper */


var RedPepper = function () {
    this.toString = function () {
        return "Red Pepper";
    };
};

RedPepper.prototype = new Veggies();
RedPepper.prototype.constructor = RedPepper;

/* ConcreteProduct - ReggianoCheese */

var ReggianoCheese = function () {
    this.toString = function () {
        return "Reggiano Cheese";
    };
};

ReggianoCheese.prototype = new Cheese();
ReggianoCheese.prototype.constructor = ReggianoCheese;

/* ConcreteProduct - SlicedPepperoni */

var SlicedPepperoni = function () {
    this.toString = function () {
        return "Sliced Pepperoni";
    };
};

SlicedPepperoni.prototype = new Pepperoni();
SlicedPepperoni.prototype.constructor = SlicedPepperoni;

/* ConcreteProduct - Spinach */

var Spinach = function () {
    this.toString = function () {
        return "Spinach";
    };
};

Spinach.prototype = new Veggies();
Spinach.prototype.constructor = Spinach;

/* ConcreteProduct - ThickCrustDough */

var ThickCrustDough = function () {
    this.toString = function () {
        return "ThickCrust style extra thick crust dough";
    };
};

ThickCrustDough.prototype = new Dough();
ThickCrustDough.prototype.constructor = ThickCrustDough;

/* ConcreteProduct - ThinCrustDough */

var ThinCrustDough = function () {
    this.toString = function () {
        return "Thin Crust Dough";
    };
};

ThinCrustDough.prototype = new Dough();
ThinCrustDough.prototype.constructor = ThinCrustDough;

/* ConcreteProduct - ParmesanCheese */

var ParmesanCheese = function () {
    this.toString = function () {
        return "Shredded Parmesan";
    };
};

ParmesanCheese.prototype = new Cheese();
ParmesanCheese.prototype.constructor = ParmesanCheese;

/* ConcreteProduct - Onion */

var Onion = function () {
    this.toString = function () {
        return "Onion";
    };
};

Onion.prototype = new Veggies();
Onion.prototype.constructor = Onion;

/* ConcreteProduct - Mushroom */

var Mushroom = function () {
    this.toString = function () {
        return "Mushrooms";
    };
};

Mushroom.prototype = new Veggies();
Mushroom.prototype.constructor = Mushroom;

/* ConcreteProduct - MozzarellaCheese */

var MozzarellaCheese = function () {
    this.toString = function () {
        return "Shredded Mozzarella";
    };
};

MozzarellaCheese.prototype = new Cheese();
MozzarellaCheese.prototype.constructor = MozzarellaCheese;

/* ConcreteProduct - MarinaraSauce */

var MarinaraSauce = function () {
    this.toString = function () {
        return "Marinara Sauce";
    };
};

MarinaraSauce.prototype = new Sauce();
MarinaraSauce.prototype.constructor = MarinaraSauce;

/* ConcreteProduct - Garlic */

var Garlic = function () {
    this.toString = function () {
        return "Garlic";
    };
};

Garlic.prototype = new Clams();
Garlic.prototype.constructor = Garlic;

/* ConcreteProduct - FrozenClams */

var FrozenClams = function () {
    this.toString = function () {
        return "Frozen Clams from Chesapeake Bay";
    };
};

FrozenClams.prototype = new Clams();
FrozenClams.prototype.constructor = FrozenClams;

/* ConcreteProduct - FreshClams */

var FreshClams = function () {
    this.toString = function () {
        return "Fresh Clams from Long Island Sound";
    };
};

FreshClams.prototype = new Clams();
FreshClams.prototype.constructor = FreshClams;

/* ConcreteProduct - Eggplant */

var Eggplant = function () {
    this.toString = function () {
        return "Eggplant";
    };
};

Eggplant.prototype = new Veggies();
Eggplant.prototype.constructor = Eggplant;

/* ConcreteProduct - BlackOlives */

var BlackOlives = function () {
    this.toString = function () {
        return "Black Olives";
    };
};

BlackOlives.prototype = new Veggies();
BlackOlives.prototype.constructor = BlackOlives;

/* AbstractClient - Pizza */

var Pizza = function () {

    // abstract prepare();
  
    this.bake = function () {
        console.log("Bake for 25 minutes at 350");
    };
  
    this.cut = function () {
        console.log("Cutting the pizza into diagonal slices");
    };
  
    this.box = function () {
        console.log("Place pizza in official PizzaStore box");
    };
  
    this.setName = function (name) {
        this.name = name;
    };
  
    this.getName = function () {
        return this.name;
    };
  
    this.toString = function () {
        var result = "";
        result += "---- " + this.name + " ----\n";
        if (this.dough) {
            result += this.dough;
            result += "\n";
        }
        if (this.sauce) {
            result += this.sauce;
            result += "\n";
        }
        if (this.cheese) {
            result += this.cheese;
            result += "\n";
        }
        if (this.veggies) {
            for (var i = 0; i < this.veggies.length; i++) {
                result += this.veggies[i];
                if (i < this.veggies.length - 1) {
                    result += ", ";
                }
            }
            result += "\n";
        }
        if (this.clam) {
            result += this.clam;
            result += "\n";
        }
        if (this.pepperoni) {
            result += this.pepperoni;
            result += "\n";
        }
        return result;
    };

};

/* ConcreteClient - PepperoniPizza */

var PepperoniPizza = function (ingredientFactory) {

    var ingredientFactory = ingredientFactory;
  
    this.prepare = function () {
        console.log("Preparing " + this.name);
        this.dough = ingredientFactory.createDough();
        this.sauce = ingredientFactory.createSauce();
        this.cheese = ingredientFactory.createCheese();
        this.veggies = ingredientFactory.createVeggies();
        this.pepperoni = ingredientFactory.createPepperoni();
    };

};

PepperoniPizza.prototype = new Pizza();
PepperoniPizza.prototype.constructor = PepperoniPizza();

/* ConcreteClient - CheesePizza */

var CheesePizza = function (ingredientFactory) {

    var ingredientFactory = ingredientFactory;
  
    this.prepare = function () {
        console.log("Preparing " + this.name);
        this.dough = ingredientFactory.createDough();
        this.sauce = ingredientFactory.createSauce();
        this.cheese = ingredientFactory.createCheese();
    };

};

CheesePizza.prototype = new Pizza();
CheesePizza.prototype.constructor = CheesePizza();

/* ConcreteClient - ClamPizza */

var ClamPizza = function (ingredientFactory) {

    var ingredientFactory = ingredientFactory;
  
    this.prepare = function () {
        console.log("Preparing " + this.name);
        this.dough = ingredientFactory.createDough();
        this.sauce = ingredientFactory.createSauce();
        this.cheese = ingredientFactory.createCheese();
        this.clam = ingredientFactory.createClam();
    };

};

ClamPizza.prototype = new Pizza();
ClamPizza.prototype.constructor = ClamPizza();

/* ConcreteClient - VeggiePizza */

var VeggiePizza = function (ingredientFactory) {

    var ingredientFactory = ingredientFactory;
  
    this.prepare = function () {
        console.log("Preparing " + this.name);
        this.dough = ingredientFactory.createDough();
        this.sauce = ingredientFactory.createSauce();
        this.cheese = ingredientFactory.createCheese();
        this.clam = ingredientFactory.createClam();
    };

};

VeggiePizza.prototype = new Pizza();
VeggiePizza.prototype.constructor = VeggiePizza();

/* Creator - PizzaStore */

var PizzaStore = function () {

    this.orderPizza = function (type) {
        var pizza = this.createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza
    };

    // abstract createPizza(item);

};

/* ConcreteCreator - NYPizzaStore */

var NYPizzaStore = function () {
    // factory method
    this.createPizza = function (item) {
        var pizza = null;
        var ingredientFactory = new NYPizzaIngredientFactory();
      
        if (item === "cheese") {

            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("New York Style Cheese Pizza");

        } else if (item === "veggie") {

            pizza = new VeggiePizza(ingredientFactory);
            pizza.setName("New York Style Veggie Pizza");

        } else if (item === "clam") {

            pizza = new ClamPizza(ingredientFactory);
            pizza.setName("New York Style Clam Pizza");

        } else if (item === "pepperoni") {

            pizza = new PepperoniPizza(ingredientFactory);
            pizza.setName("New York Style Pepperoni Pizza");

        }
        return pizza;
    };
};

NYPizzaStore.prototype = new PizzaStore();
NYPizzaStore.prototype.constructor = NYPizzaStore;

/* ConcreteCreator - NYPizzaStore */

var ChicagoPizzaStore = function () {
    // factory method
    this.createPizza = function (item) {
        var pizza = null;
        var ingredientFactory = new NYPizzaIngredientFactory();
      
        if (item === "cheese") {

            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("Chicago Style Cheese Pizza");

        } else if (item === "veggie") {

            pizza = new VeggiePizza(ingredientFactory);
            pizza.setName("Chicago Style Veggie Pizza");

        } else if (item === "clam") {

            pizza = new ClamPizza(ingredientFactory);
            pizza.setName("Chicago Style Clam Pizza");

        } else if (item === "pepperoni") {

            pizza = new PepperoniPizza(ingredientFactory);
            pizza.setName("Chicago Style Pepperoni Pizza");

        }
        return pizza;
    };
};

ChicagoPizzaStore.prototype = new PizzaStore();
ChicagoPizzaStore.prototype.constructor = ChicagoPizzaStore;

/* test application */

var Application = function () {
    this.run = function () {
      
        var nyStore = new NYPizzaStore();
        var chicagoStore = new ChicagoPizzaStore();

        var pizza = nyStore.orderPizza("cheese");
        console.log("Ethan ordered a " + pizza + "\n");

        pizza = chicagoStore.orderPizza("cheese");
        console.log("Joel ordered a " + pizza + "\n");

        pizza = nyStore.orderPizza("clam");
        console.log("Ethan ordered a " + pizza + "\n");

        pizza = chicagoStore.orderPizza("clam");
        console.log("Joel ordered a " + pizza + "\n");

        pizza = nyStore.orderPizza("pepperoni");
        console.log("Ethan ordered a " + pizza + "\n");

        pizza = chicagoStore.orderPizza("pepperoni");
        console.log("Joel ordered a " + pizza + "\n");

        pizza = nyStore.orderPizza("veggie");
        console.log("Ethan ordered a " + pizza + "\n");

        pizza = chicagoStore.orderPizza("veggie");
        console.log("Joel ordered a " + pizza + "\n");
      
    };
};

var application = new Application();
application.run();


На выходе:

Preparing New York Style Cheese Pizza
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Ethan ordered a ---- New York Style Cheese Pizza ----
Thin Crust Dough
Marinara Sauce
Reggiano Cheese

Preparing Chicago Style Cheese Pizza
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Joel ordered a ---- Chicago Style Cheese Pizza ----
Thin Crust Dough
Marinara Sauce
Reggiano Cheese

Preparing New York Style Clam Pizza
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Ethan ordered a ---- New York Style Clam Pizza ----
Thin Crust Dough
Marinara Sauce
Reggiano Cheese
Fresh Clams from Long Island Sound

Preparing Chicago Style Clam Pizza
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Joel ordered a ---- Chicago Style Clam Pizza ----
Thin Crust Dough
Marinara Sauce
Reggiano Cheese
Fresh Clams from Long Island Sound

Preparing New York Style Pepperoni Pizza
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Ethan ordered a ---- New York Style Pepperoni Pizza ----
Thin Crust Dough
Marinara Sauce
Reggiano Cheese
Garlic, Onion, Mushrooms, Red Pepper
Sliced Pepperoni

Preparing Chicago Style Pepperoni Pizza
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Joel ordered a ---- Chicago Style Pepperoni Pizza ----
Thin Crust Dough
Marinara Sauce
Reggiano Cheese
Garlic, Onion, Mushrooms, Red Pepper
Sliced Pepperoni

Preparing New York Style Veggie Pizza
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Ethan ordered a ---- New York Style Veggie Pizza ----
Thin Crust Dough
Marinara Sauce
Reggiano Cheese
Fresh Clams from Long Island Sound

Preparing Chicago Style Veggie Pizza
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Joel ordered a ---- Chicago Style Veggie Pizza ----
Thin Crust Dough
Marinara Sauce
Reggiano Cheese
Fresh Clams from Long Island Sound


Если у вас возникло желание получше разобрать данный пример, то лучше обратиться к книжке "Паттерны проектирования. Эрик Фримен, Элизабет Фримен, Кэтти Сьерра, Берт Бейтс".