Паттерн Factory (Фабрика), теория

Ранее уже разбирались паттерн Singleton (создание единственного в своём роде объекта) и паттерн Prototype (создание объектов на основе общего шаблона). В обоих этих методах объекты создаются «вручную».

А что если требуется создать одну функцию/класс, который может «производить» объекты разных типов, чтобы в каждом внутри была «зашита» собственная логика и данные?

Для этого можно использовать паттерн Factory (Фабрика). Он представляет собой функцию или метод, принимающий параметры, и на основе параметров создающий объект нужного типа («фабрика» производит объекты):

Схема паттерна Factory

В упрощенном виде фабрика — это одна функция/«суперкласс», который отвечает за всё сразу:

fileProcessor.js
export class FileProcessor {
static processFile(type, content) {
if (type === "image") {
// Уникальная логика для изображений
return {
type,
content,
createImage() {},
};
} else if (type === "text") {
// Уникальная логика для текстовых файлов
return {
type,
content,
createTextFile() {},
};
} else if (type === "pdf") {
// Уникальная логика для PDF-файлов
return {
type,
content,
createPDF() {},
};
}
}
}
app.js
import { FileProcessor } from "./fileProcessor";
// Использование
const imageFile = FileProcessor.processFile("image", "photo.jpg");
imageFile.createImage(); // Создание изображения…
const textFile = FileProcessor.processFile("text", "Hello World");
textFile.createTextFile(); // Создание текстового файла…

У такого подхода есть очевидные недостатки:

Поэтому такой вариант реализации подходит для случая, если логика несложная, типов возможных объектов немного или не планируется добавлять новые.

Но могут быть другие требования и условия:

При таких условиях стоит выделить каждый «фабричный» тип объекта в отдельный класс/функцию. Как будет выглядеть класс фабрики с таким подходом:

fileFactory.js
// Базовый класс File
class File {
constructor(content) {
this.content = content;
}
create() {
throw new Error("create() должен быть имплементирован");
}
}
// Конкретные классы разных документов
class Image extends File {
create() {
// логика создания изображения
}
}
class TextFile extends File {
create() {
// логика создания текстового документа
}
}
class PDFFile extends File {
create() {
// логика создания PDF-файла
}
}
// Фабрика
export class FileFactory {
static processFile(type, content) {
switch (type) {
case "image":
return new Image(content);
case "text":
return new TextFile(content);
case "pdf":
return new PDFFile(content);
default:
throw new Error("Неизвестный тип документа");
}
}
}
app.js
import { FileFactory } from "./fileFactory";
// Использование
const imageFile = FileFactory.processFile("image", "photo.jpg");
imageFile.create(); // Создание изображения…
const textFile = FileFactory.processFile("text", "Hello World");
textFile.create(); // Создание текстового файла…

Что получаем с таким подходом:

Но при этом такой «развесистый» подход не стоит использовать: