Skip to main content

Abstract Factory - Design Pattern

Creational: Abstract Factory Pattern



What is the Abstract Factory Pattern in JavaScript?

View Answer:
Interview Response: The Abstract Factory Pattern is a creational pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes.

Technical Response: It's also a good idea to be familiar with the Abstract Factory design. Which seeks to encapsulate a set of independent factories with a similar purpose, and it separates the details of implementing a set of objects from their general usage.


Diagram:



The objects participating in this pattern are:

AbstractFactory -- not used in JavaScript

  • declares an interface for creating products

ConcreteFactory -- In example code: EmployeeFactory, VendorFactory

  • a factory object that manufactures new products
  • the create() method returns new products

Products -- In example code: Employee, Vendor

  • the factory-created product instances

AbstractProduct -- not used in JavaScript

  • declares an interface for the created products

note

Though the definition particularly mentions that an interface needs to be defined, we don’t have interfaces in Vanilla JavaScript. Therefore, we must implement it in a way that JavaScript translates into an interface.


Code Example:

Here's an example of the Abstract Factory pattern in JavaScript...

// Abstract factory
class CarFactory {
createEngine() {
throw new Error("This method must be overwritten!");
}

createTransmission() {
throw new Error("This method must be overwritten!");
}
}

// Concrete factories
class ToyotaFactory extends CarFactory {
createEngine() {
return new V6Engine();
}

createTransmission() {
return new AutomaticTransmission();
}
}

class FordFactory extends CarFactory {
createEngine() {
return new V8Engine();
}

createTransmission() {
return new ManualTransmission();
}
}

// Abstract products
class Engine {
description() {
throw new Error("This method must be overwritten!");
}
}

class Transmission {
type() {
throw new Error("This method must be overwritten!");
}
}

// Concrete products
class V6Engine extends Engine {
description() {
return "V6 engine";
}
}

class V8Engine extends Engine {
description() {
return "V8 engine";
}
}

class ManualTransmission extends Transmission {
type() {
return "Manual transmission";
}
}

class AutomaticTransmission extends Transmission {
type() {
return "Automatic transmission";
}
}

// Usage
let factory = new ToyotaFactory();
let engine = factory.createEngine();
let transmission = factory.createTransmission();

console.log(engine.description()); // V6 engine
console.log(transmission.type()); // Automatic transmission

factory = new FordFactory();
engine = factory.createEngine();
transmission = factory.createTransmission();

console.log(engine.description()); // V8 engine
console.log(transmission.type()); // Manual transmission

In this example, CarFactory is the abstract factory, ToyotaFactory and FordFactory are concrete factories, Engine and Transmission are abstract products, and V6Engine, V8Engine, ManualTransmission, and AutomaticTransmission are concrete products.


note

Please note, JavaScript does not have a concept of 'abstract' classes or interfaces like other languages (Java, C#). But we can simulate this behavior by throwing errors in base 'abstract' classes when trying to use them directly or when a derived class does not implement a required method.


The Abstract Factory pattern belongs to which pattern family?

View Answer:
Interview Response: The abstract factory pattern belongs to the "creational" design pattern category, which is used to create objects in a flexible and extensible manner.

Why would you delegate the responsibility of object construction to others rather than directly calling a constructor with the new keyword?

View Answer:
Interview Response: Delegating object construction promotes loose coupling, testability, and flexibility, allowing you to switch between different product families without changing the client code.

Technical Response: When object creation is tightly coupled to object usage, changes to object construction logic can have a ripple effect throughout the codebase. Delegating this responsibility to an abstract factory promotes loose coupling, allowing for easier maintenance, testing, and swapping out of object implementations, while also enabling the creation of related object families with a unified interface.

What are the objects participants used in the Abstract Factory Pattern?

View Answer:
Technical Response: The objects participants used in the Abstract Factory Pattern include the AbstractFactory, ConcreteFactory, ConcreteProducts, and AbstractProduct.

Technical Response: The objects participants used in the Abstract Factory Pattern include the AbstractFactory, ConcreteFactory, Products, and AbstractProduct. The AbstractFactory, not used in JavaScript, declares an interface for creating products. The ConcreteFactory is a factory object that manufactures new products, and products are simply instances created by the factory. The AbstractFactory participant, not used in JavaScript, declares an interface for the produced products.

Code Example:

// 1. Abstract Factory
class FurnitureFactory {
createChair() {
throw new Error("This method must be overwritten!");
}

createSofa() {
throw new Error("This method must be overwritten!");
}
}

// 2. Concrete Factories
class ModernFurnitureFactory extends FurnitureFactory {
createChair() {
return new ModernChair();
}

createSofa() {
return new ModernSofa();
}
}

class VintageFurnitureFactory extends FurnitureFactory {
createChair() {
return new VintageChair();
}

createSofa() {
return new VintageSofa();
}
}

// 3. Abstract Products
class Chair {
description() {
throw new Error("This method must be overwritten!");
}
}

class Sofa {
description() {
throw new Error("This method must be overwritten!");
}
}

// 4. Concrete Products
class ModernChair extends Chair {
description() {
return "Modern Chair";
}
}

class ModernSofa extends Sofa {
description() {
return "Modern Sofa";
}
}

class VintageChair extends Chair {
description() {
return "Vintage Chair";
}
}

class VintageSofa extends Sofa {
description() {
return "Vintage Sofa";
}
}

// Usage
let factory = new ModernFurnitureFactory();
let chair = factory.createChair();
let sofa = factory.createSofa();

console.log(chair.description()); // Modern Chair
console.log(sofa.description()); // Modern Sofa

factory = new VintageFurnitureFactory();
chair = factory.createChair();
sofa = factory.createSofa();

console.log(chair.description()); // Vintage Chair
console.log(sofa.description()); // Vintage Sofa

In this example, FurnitureFactory is the Abstract Factory, ModernFurnitureFactory and VintageFurnitureFactory are Concrete Factories, Chair and Sofa are Abstract Products, and ModernChair, ModernSofa, VintageChair, and VintageSofa are Concrete Products.


note

Though the definition particularly mentions that an interface needs to be defined, we don’t have interfaces in Vanilla JavaScript. Therefore, we must implement it in a way that JavaScript translates into an interface.


What are some of the benefits of using the Abstract factory pattern?

View Answer:
Interview Response: Benefits include independence from concrete classes, improved scalability, adhering to the Single Responsibility and Open/Closed Principles, and enhanced testability.

The abstract factory pattern offers various advantages, which we can describe in the following fashion:

  • We ensure the compatibility of items produced by the same factory class.
  • Open-closed Concept: Clean code, since we introduce new product families without affecting the current code structure, ensuring the open-closed concept.
  • Cleaner code since the single responsibility principle (SRP) gets followed because the obligation for generating the concrete product gets passed to the concrete creator class rather than the client class.


What are some of the disadvantages of implementing the Abstract Factory pattern?

View Answer:
Interview Response: Disadvantages can be unnecessary complexity for simple scenarios, larger codebase, and potential type safety issues.

Technical Response: Some disadvantages of using the abstract factory pattern include increased complexity and overhead compared to simpler object creation methods, as well as potential difficulty in extending the pattern to accommodate new object types.

Are there any alternatives to using the Abstract Factory?

View Answer:
Interview Response: Alternatives include the Factory Method, Builder, and Prototype patterns, or Dependency Injection techniques. Each of these patterns has its own strengths and weaknesses.

Technical Response: There are several alternative patterns that can be used instead of the Abstract Factory pattern, depending on the specific requirements of the application. Some examples include the Factory Method pattern, the Builder pattern, and the Prototype pattern. Each of these patterns has its own strengths and weaknesses and can be used to solve different problems related to object creation and instantiation.

Why would you use the Abstract Factory Pattern?

View Answer:
Interview Response: It's used when a system should be independent of how its products are created, composed and represented, offering a high-level interface for client-side use.

Can Abstract Factory Pattern enhance scalability?

View Answer:
Interview Response: Yes, it allows for the introduction of new products or families of products without breaking existing client code.

How does Abstract Factory Pattern relate to Single Responsibility Principle?

View Answer:
Interview Response: It adheres to it by separating the construction of complex objects from their representation, each factory is responsible for creating a certain product family.

How is the Abstract Factory different from the Factory Method pattern?

View Answer:
Interview Response: The Abstract Factory creates families of related objects without specifying their concrete classes, while Factory Method deals with the problem of creating a single product.

How can Abstract Factory Pattern help with testability?

View Answer:
Interview Response: It allows for easy swapping of object implementations, which can be beneficial for mocking objects in testing scenarios.

Technical Response: The Abstract Factory Pattern can aid with testability by allowing you to easily swap out concrete implementations with mock objects for unit testing. This is because the pattern allows the instantiation of classes to be abstracted, which in turn, allows you to substitute different implementations for testing.

Code Example:

// Abstract factory
class DBFactory {
connect() {
throw new Error("This method must be overwritten!");
}

query() {
throw new Error("This method must be overwritten!");
}
}

// Concrete factory
class MySQLFactory extends DBFactory {
connect() {
console.log("Connecting to MySQL database...");
}

query() {
console.log("Querying data from MySQL database...");
}
}

// Mock factory for testing
class MockDBFactory extends DBFactory {
connect() {
console.log("Mock: Connecting to database...");
}

query() {
console.log("Mock: Querying data from database...");
}
}

// Class that uses the factory
class UserService {
constructor(dbFactory) {
this.dbFactory = dbFactory;
}

getUser() {
this.dbFactory.connect();
this.dbFactory.query();
}
}

// Normal usage
let userService = new UserService(new MySQLFactory());
userService.getUser(); // Connecting to MySQL database... Querying data from MySQL database...

// Test usage
userService = new UserService(new MockDBFactory());
userService.getUser(); // Mock: Connecting to database... Mock: Querying data from database...

In this example, DBFactory is the Abstract Factory, MySQLFactory is a Concrete Factory, and MockDBFactory is a mock factory for testing. UserService is a class that uses the factory.

When we want to test the getUser function of the UserService class, we can inject a mock DB factory (MockDBFactory), instead of the real DB factory (MySQLFactory). This makes it easier to test getUser in isolation, without having to connect to a real MySQL database.


What's the relationship between Abstract Factory and Dependency Injection?

View Answer:
Interview Response: Dependency Injection is a form of Abstract Factory. Instead of creating objects, dependencies are injected, promoting loose coupling and enhancing testability.

Interview Response: Dependency Injection (DI) is a design pattern that deals with how components get hold of their dependencies. The Abstract Factory pattern can be used to instantiate a family of related objects at runtime, and it is commonly used in combination with Dependency Injection.

The Abstract Factory pattern can be used in the implementation of the Dependency Injection pattern, where it might be called an "Injector". Instead of directly constructing the objects, the Injector uses the Abstract Factory to create them. This allows for much greater flexibility in the creation of objects, as the actual classes used can be configured at runtime.

Code Example:

Here is an example of how you can use the Abstract Factory and Dependency Injection patterns together in JavaScript...

// Abstract Factory
class LoggerFactory {
createLogger() {
throw new Error("This method must be overwritten!");
}
}

// Concrete Factories
class ConsoleLoggerFactory extends LoggerFactory {
createLogger() {
return new ConsoleLogger();
}
}

class FileLoggerFactory extends LoggerFactory {
createLogger() {
return new FileLogger();
}
}

// Products (Loggers)
class ConsoleLogger {
log(message) {
console.log(`Console log: ${message}`);
}
}

class FileLogger {
log(message) {
console.log(`File log: ${message}`);
}
}

// Service that depends on the LoggerFactory
class UserService {
constructor(loggerFactory) {
this.logger = loggerFactory.createLogger();
}

doSomething() {
this.logger.log("User service is doing something...");
}
}

// Usage without dependency injection
let userService = new UserService(new ConsoleLoggerFactory());
userService.doSomething(); // Console log: User service is doing something...

// Usage with dependency injection
userService = new UserService(new FileLoggerFactory());
userService.doSomething(); // File log: User service is doing something...

In this example, LoggerFactory is the Abstract Factory, ConsoleLoggerFactory and FileLoggerFactory are Concrete Factories, ConsoleLogger and FileLogger are Loggers (Products), and UserService is a service that depends on the LoggerFactory. We use dependency injection to inject the LoggerFactory into the UserService.


Can Abstract Factory Pattern cause problems with type safety?

View Answer:
Interview Response: Yes, it can. If the client doesn't know the specific types of objects produced by the factory, type safety issues could potentially arise due to JavaScript's dynamic typing.

Can the Abstract Factory Pattern contribute to better software maintenance?

View Answer:
Interview Response: Yes, it allows changing the underlying implementation without modifying the client code, promoting low coupling and high cohesion, which benefits maintenance.

Can Abstract Factory be used with Singleton?

View Answer:
Interview Response: Yes, you can use the Singleton and Abstract Factory design patterns together. A Singleton ensures that a class has only one instance and provides a global point of access to it. You can use a Singleton as your Abstract Factory.

Code Example:

// Singleton Abstract Factory
class CarFactory {
static instance = null;

constructor() {
if (CarFactory.instance) {
return CarFactory.instance;
}

CarFactory.instance = this;
}

createCar(brand) {
switch (brand) {
case 'Toyota':
return new Toyota();
case 'Ford':
return new Ford();
default:
throw new Error("This brand is not supported");
}
}
}

// Concrete products
class Toyota {
description() {
return 'Toyota Car';
}
}

class Ford {
description() {
return 'Ford Car';
}
}

// Usage
const factory1 = new CarFactory();
const factory2 = new CarFactory();

console.log(factory1 === factory2); // true, meaning both factories are the same instance

const car1 = factory1.createCar('Toyota');
const car2 = factory2.createCar('Ford');

console.log(car1.description()); // Toyota Car
console.log(car2.description()); // Ford Car

In this example, CarFactory is a Singleton Abstract Factory. It has a static instance property to ensure only one instance of CarFactory exists. The createCar method is used to create cars, and it's our abstract factory method.

Toyota and Ford are concrete products. The description method is used to describe each product.

In the usage part, factory1 and factory2 are both instances of the CarFactory singleton. We verify that they're the same instance by comparing them with ===, which returns true. We then use these factories to create Toyota and Ford cars.


How does the Abstract Factory Pattern support Open/Closed Principle?

View Answer:
Interview Response: It promotes the Open/Closed Principle as new types of products can be added without changing existing client code.

Can the Abstract Factory Pattern help with performance optimization?

View Answer:
Interview Response: Indirectly, it can. It doesn't inherently optimize performance, but it can promote better organization and structure which can lead to performance benefits.

When should you avoid using the Abstract Factory Pattern?

View Answer:
Interview Response: Avoid it when you're dealing with a few simple objects or when the families of products are not clearly defined.

Code Example:

// Simple class without Abstract Factory pattern
class Cat {
constructor(name) {
this.name = name;
}

say() {
console.log(`${this.name} says: Meow!`);
}
}

const kitty = new Cat("Kitty");
kitty.say(); // Kitty says: Meow!

In this example, the Cat class is simple and has no dependencies on other objects. Using the Abstract Factory pattern to create Cat objects would add unnecessary complexity. In this case, it's simpler and more straightforward to just use the new keyword to create Cat objects directly.


How does the Abstract Factory Pattern promote polymorphism?

View Answer:
Interview Response: It provides an interface for creating families of objects, allowing different factories to produce objects with the same interface but different implementations.

Does the Abstract Factory Pattern support inversion of control?

View Answer:
Interview Response: Yes, it helps implement inversion of control by defining an interface for creating objects, leaving the specifics to concrete factories.

How does the Abstract Factory Pattern relate to the concept of "Loose Coupling"?

View Answer:
Interview Response: It encapsulates the responsibility of creating object families into separate factories, allowing client code to depend on abstractions rather than concrete classes.

Can the Abstract Factory Pattern work with Prototype Pattern?

View Answer:
Interview Response: Yes, the Abstract Factory Pattern can work with the Prototype Pattern in JavaScript. The Prototype Pattern involves creating objects based on a template of an existing object through cloning.

Code Example:

Here's a code example that uses both patterns.

// Abstract factory
class AnimalFactory {
constructor(dogPrototype, catPrototype) {
this.dogPrototype = dogPrototype;
this.catPrototype = catPrototype;
}

createDog() {
return this.dogPrototype.clone();
}

createCat() {
return this.catPrototype.clone();
}
}

// Prototypes
class Dog {
constructor(name) {
this.name = name;
}

clone() {
return new Dog(this.name);
}

bark() {
return `${this.name} says: Woof!`;
}
}

class Cat {
constructor(name) {
this.name = name;
}

clone() {
return new Cat(this.name);
}

meow() {
return `${this.name} says: Meow!`;
}
}

// Usage
const dogPrototype = new Dog("Fido");
const catPrototype = new Cat("Whiskers");

const animalFactory = new AnimalFactory(dogPrototype, catPrototype);

const dog1 = animalFactory.createDog();
console.log(dog1.bark()); // Fido says: Woof!

const cat1 = animalFactory.createCat();
console.log(cat1.meow()); // Whiskers says: Meow!

const dog2 = animalFactory.createDog();
console.log(dog2.bark()); // Fido says: Woof!

const cat2 = animalFactory.createCat();
console.log(cat2.meow()); // Whiskers says: Meow!

In this example, AnimalFactory is an abstract factory that uses prototypes to create new animals. It uses the clone method to create new dogs and cats based on the dogPrototype and catPrototype. Dog and Cat are prototypes, and they implement the clone method to create new dogs and cats.