JavaScript and Design Patterns - Chapter 4

Cover Image for JavaScript and Design Patterns - Chapter 4
Lazar Stankovic
Lazar Stankovic

πŸ€“ 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.


[@portabletext/react] Unknown block type "image", specify a component for it in the `components.types` prop


🐯 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


πŸ™ THANK YOU FOR READING!