Programming
JavaScript and Design Patterns - Chapter 4
π€ INTRODUCTION
Hello, my dear coders! Today, we are discussing the Abstract Factory design pattern. π
π FACTORY STORIES
There are so many stories you could use to describe the Abstract Factory Design Pattern, I will use a couple of the most popular ones. But let's say something about the Abstract Factory pattern, a definition of the sort.
Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.
Abstract Factory suggests defining an interface for creating an object where you allow the subclasses to decide which class to instantiate. This pattern handles the problem by defining a completely separate method for the creation of objects and which sub-classes are able to override so they can specify the 'type' of the factory product that will be created.
ποΈ THE FURNITURE FACTORY STORY
Let's say that you want to build software that will be used by the furniture shop. You will structure your code so that you have specific classes that will represent of:
- A family of related products (Chairs, CoffeeTables, DinnerTables...)
- Several variants of the mentioned family. For example, Chair, CoffeeTables, DinnerTables may be available in different styling variants (Traditional, Casual, Contemporary...)
So, you will need to create individual furniture items so that they match other objects of the same family, but you don't want to change the existing code when adding new products or families of products to the program.
π THE VEHICLE-MOTOR FACTORY STORY
For example, a class Vehicle that has a member Motor, but no concrete type of Motor defined in advance, can be constructed by telling the Vehicle constructor to use an electric motor or a gasoline motor. Also, a class Vehicle with a member Motor defined with a dynamic type can have subclasses of type, like an electric plane or an old car, each constructed with a different type of Motor. This can be accomplished by constructing the subclasses with a Vehicle factory method while supplying the motor type.
π¦ THE ANIMAL FACTORY STORY
Let's say you want to develop a game, that will have the world, the continents, and you need a generator, that will generate different animal species. In that case, you will have Continent Factory, many concrete continent factories, for example, Africa Factory and America Factory. Then, you can have different categories of animals, for example, Herbivores and Carnivores. Those are Animal Factory classes. Their respected concrete classes could be Lion (Carnivore), Bison(Herbivore), wildebeest (Herbivore), Wolf (Carnivore)...
You see where am I going with this?
π©βπ» GENERIC CODE EXAMPLE
1//Generic Abstract Factory class
2class AbstractFactory {
3 //methods for creating products
4 createProductA() {
5 return;
6 }
7 createProductB() {
8 return;
9 }
10}
11//Generic Concrete Factory class that inherits an Abstract Factory Class
12class ConcreteFactory1 extends AbstractFactory {
13 //overridden method for create a specific ProductA1 product
14 createProductA() {
15 return new ProductA1();
16 }
17 //overridden method for create a specific ProductB1 product
18 createProductB() {
19 return new ProductB1();
20 }
21}
22//Generic Concrete Factory class that inherits an Abstract Factory Class
23class ConcreteFactory2 extends AbstractFactory {
24 //overridden method for create a specific ProductA2 product
25 createProductA() {
26 return new ProductA2();
27 }
28 //overridden method for create a specific ProductB2 product
29 createProductB() {
30 return new ProductB2();
31 }
32}
33//Abstract product A class
34class AbstractProductA {}
35//Abstract product B class with a single method that will be overridden
36class AbstractProductB {
37 interact(abstractProductA) {}
38}
39//Product A1 inherits AbstractProductA
40class ProductA1 extends AbstractProductA {}
41//Product B1 inherits AbstractProductB implements the interact method
42class ProductB1 extends AbstractProductB {
43 interact(abstractProductA) {
44 //returns type of the current object (Object) and the type of the function parameter
45 return 'ProductB1 interacts AbstractProductA';
46 }
47}
48//Product A2 inherits AbstractProductA
49class ProductA2 extends AbstractProductA {}
50//Product B2 inherits AbstractProductB implements the interact method
51class ProductB2 extends AbstractProductB {
52 interact(abstractProductA) {
53 return 'ProductB2 interacts AbstractProductB';
54 }
55}
56//Client class
57class Client {
58 //constructor takes concrete factory class instance
59 constructor(abstractFactory) {
60 //creating the products
61 this.abstractProductB = abstractFactory.createProductB();
62 this.abstractProductA = abstractFactory.createProductA();
63 }
64
65 //example of product interaction
66 run() {
67 return this.abstractProductB.interact(this.abstractProductA);
68 }
69}
70
71var factory_1 = new ConcreteFactory1();
72var client_1 = new Client(factory_1);
73console.log(client_1.run());
74
75var factory_2 = new ConcreteFactory2();
76var client_2 = new Client(factory_2);
77console.log(client_2.run());
This is a generic example of the Abstract Factory Design Pattern, I will include the visual representation for the visual learners.
π― THE ANIMAL WORLD FACTORY EXAMPLE
1//Generic Abstract Factory class
2class ContinentFactory {
3 //methods for creating products
4 createHerbivore() {
5 return;
6 }
7 createCarnivore() {
8 return;
9 }
10}
11class AfricaFactory extends ContinentFactory {
12 //overridden method for create a specific ProductA1 product
13 createHerbivore() {
14 return new Wildebeest();
15 }
16 //overridden method for create a specific ProductB1 product
17 createCarnivore() {
18 return new Lion();
19 }
20}
21//Generic Concrete Factory class that inherits an Abstract Factory Class
22class AmericaFactory extends ContinentFactory {
23 //overridden method for create a specific ProductA2 product
24 createHerbivore() {
25 return new Bison();
26 }
27 //overridden method for create a specific ProductB2 product
28 createCarnivore() {
29 return new Wolf();
30 }
31}
32//Abstract product A class
33class Herbivore {}
34//Abstract product B class with a single method that will be overridden
35class Carnivore {
36 eat(herbivore) {}
37}
38//Product A1 inherits AbstractProductA
39class Wildebeest extends Herbivore {
40 constructor() {
41 super();
42 this.name = 'Wildebeest';
43 }
44}
45//Product B1 inherits AbstractProductB implements the interact method
46class Lion extends Carnivore {
47 constructor() {
48 super();
49 this.name = 'Lion';
50 }
51 eat(herbivore) {
52 //returns type of the current object (Object) and the type of the function parameter
53 return this.name + ' eats ' + herbivore.name;
54 }
55}
56//Product A2 inherits AbstractProductA
57class Bison extends Herbivore {
58 constructor() {
59 super();
60 this.name = 'Bison';
61 }
62}
63//Product B2 inherits AbstractProductB implements the interact method
64class Wolf extends Carnivore {
65 constructor() {
66 super();
67 this.name = 'Wolf';
68 }
69 eat(herbivore) {
70 return this.name + ' eats ' + herbivore.name;
71 }
72}
73//Client class
74class AnimalWorld {
75 //constructor takes concrete factory class instance
76 constructor(continent) {
77 //creating the products
78 this.carnivore = continent.createCarnivore();
79 this.herbivore = continent.createHerbivore();
80 }
81
82 //example of product interaction
83 start() {
84 return this.carnivore.eat(this.herbivore);
85 }
86}
87
88var africa = new AfricaFactory();
89var animalWorld = new AnimalWorld(africa);
90console.log(animalWorld.start());
91
92//Output: Lion eats Wildebeest
93
94var america = new AmericaFactory();
95var animalWorld_2 = new AnimalWorld(america);
96console.log(animalWorld_2.start());
97
98//Output: Wolf eats Bison
π‘ WHEN TO USE THE ABSTRACT FACTORY DESIGN PATTERN
Use the Abstract Factory Design Pattern when your code needs to work with various families of related products, but you don't want it to depend on the concrete classes of those products - they might be unknown beforehand or you simply want to allow for future extensibility.
β
PROS
- You can be sure that the products youβre getting from a factory are compatible with each other
- You avoid tight coupling between concrete products and client code
- Single Responsibility Principle. You can extract the product creation code into one place, making the code easier to support
- Open/Closed Principle. You can introduce new variants of products without breaking existing client code
β CONS
- The code may become more complicated than it should be since a lot of new interfaces and classes are introduced along with the pattern