Abstract Factory - Design Pattern
Creational: Abstract Factory Pattern
What is the Abstract Factory Pattern in JavaScript?
View Answer:
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
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.
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.
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:
Why would you delegate the responsibility of object construction to others rather than directly calling a constructor with the new keyword?
View Answer:
What are the objects participants used in the Abstract Factory Pattern?
View Answer:
// 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.
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:
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:
Are there any alternatives to using the Abstract Factory?
View Answer:
Why would you use the Abstract Factory Pattern?
View Answer:
Can Abstract Factory Pattern enhance scalability?
View Answer:
How does Abstract Factory Pattern relate to Single Responsibility Principle?
View Answer:
How is the Abstract Factory different from the Factory Method pattern?
View Answer:
How can Abstract Factory Pattern help with testability?
View Answer:
// 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:
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.
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:
Can the Abstract Factory Pattern contribute to better software maintenance?
View Answer:
Can Abstract Factory be used with Singleton?
View Answer:
// 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:
Can the Abstract Factory Pattern help with performance optimization?
View Answer:
When should you avoid using the Abstract Factory Pattern?
View Answer:
// 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:
Does the Abstract Factory Pattern support inversion of control?
View Answer:
How does the Abstract Factory Pattern relate to the concept of "Loose Coupling"?
View Answer:
Can the Abstract Factory Pattern work with Prototype Pattern?
View Answer:
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.