Порождающие паттерны
Abstract Factory
Паттерн Abstract Factory
Abstract Factory — это порождающий паттерн проектирования, который позволяет создавать семейства связанных объектов, не привязываясь к конкретным классам создаваемых объектов.
Проблема
Представьте, что вы создаете симулятор мебельного магазина. Код состоит из классов, которые представляют:
- Семейство продуктов:
Chair(стул) +Sofa(диван) +CoffeeTable(журнальный столик) - Несколько вариантов этого семейства:
Modern(модерн),Victorian(викторианский),ArtDeco(ар-деко)
Вам нужен способ создавать отдельные объекты мебели так, чтобы они сочетались друг с другом. Кроме того, вы не хотите изменять существующий код при добавлении новых продуктов или семейств.
Решение
Паттерн Abstract Factory предлагает выделить общий интерфейс для создания различных типов мебели. Каждый вариант стиля будет реализован в отдельном классе-фабрике.
Структура
AbstractFactory
├── createProductA(): AbstractProductA
└── createProductB(): AbstractProductB
ConcreteFactory1
├── createProductA(): ConcreteProductA1
└── createProductB(): ConcreteProductB1
ConcreteFactory2
├── createProductA(): ConcreteProductA2
└── createProductB(): ConcreteProductB2
AbstractProductA
└── usefulFunctionA(): string
AbstractProductB
├── usefulFunctionB(): string
└── anotherUsefulFunctionB(collaborator: AbstractProductA): string
Пример реализации
JavaScript
// Абстрактная фабрика
class FurnitureFactory {
createChair() {
throw new Error('Метод createChair должен быть переопределен');
}
createSofa() {
throw new Error('Метод createSofa должен быть переопределен');
}
createCoffeeTable() {
throw new Error('Метод createCoffeeTable должен быть переопределен');
}
}
// Конкретные фабрики
class ModernFurnitureFactory extends FurnitureFactory {
createChair() {
return new ModernChair();
}
createSofa() {
return new ModernSofa();
}
createCoffeeTable() {
return new ModernCoffeeTable();
}
}
class VictorianFurnitureFactory extends FurnitureFactory {
createChair() {
return new VictorianChair();
}
createSofa() {
return new VictorianSofa();
}
createCoffeeTable() {
return new VictorianCoffeeTable();
}
}
// Абстрактные продукты
class Chair {
sitOn() {
throw new Error('Метод sitOn должен быть переопределен');
}
}
class Sofa {
lieOn() {
throw new Error('Метод lieOn должен быть переопределен');
}
}
class CoffeeTable {
putOn() {
throw new Error('Метод putOn должен быть переопределен');
}
}
// Конкретные продукты - Modern
class ModernChair extends Chair {
sitOn() {
return 'Сижу на современном стуле';
}
}
class ModernSofa extends Sofa {
lieOn() {
return 'Лежу на современном диване';
}
}
class ModernCoffeeTable extends CoffeeTable {
putOn() {
return 'Ставлю на современный журнальный столик';
}
}
// Конкретные продукты - Victorian
class VictorianChair extends Chair {
sitOn() {
return 'Сижу на викторианском стуле';
}
}
class VictorianSofa extends Sofa {
lieOn() {
return 'Лежу на викторианском диване';
}
}
class VictorianCoffeeTable extends CoffeeTable {
putOn() {
return 'Ставлю на викторианский журнальный столик';
}
}
// Клиентский код
function createFurnitureSet(factory) {
const chair = factory.createChair();
const sofa = factory.createSofa();
const coffeeTable = factory.createCoffeeTable();
console.log('Создаем набор мебели:');
console.log(chair.sitOn());
console.log(sofa.lieOn());
console.log(coffeeTable.putOn());
}
// Использование
const modernFactory = new ModernFurnitureFactory();
const victorianFactory = new VictorianFurnitureFactory();
createFurnitureSet(modernFactory);
createFurnitureSet(victorianFactory);
TypeScript с интерфейсами
// Интерфейсы продуктов
interface Button {
render(): string;
onClick(): void;
}
interface Checkbox {
render(): string;
onCheck(): void;
}
// Интерфейс абстрактной фабрики
interface UIFactory {
createButton(): Button;
createCheckbox(): Checkbox;
}
// Конкретные продукты - Windows
class WindowsButton implements Button {
render(): string {
return 'Windows Button';
}
onClick(): void {
console.log('Windows button clicked');
}
}
class WindowsCheckbox implements Checkbox {
render(): string {
return 'Windows Checkbox';
}
onCheck(): void {
console.log('Windows checkbox checked');
}
}
// Конкретные продукты - Mac
class MacButton implements Button {
render(): string {
return 'Mac Button';
}
onClick(): void {
console.log('Mac button clicked');
}
}
class MacCheckbox implements Checkbox {
render(): string {
return 'Mac Checkbox';
}
onCheck(): void {
console.log('Mac checkbox checked');
}
}
// Конкретные фабрики
class WindowsUIFactory implements UIFactory {
createButton(): Button {
return new WindowsButton();
}
createCheckbox(): Checkbox {
return new WindowsCheckbox();
}
}
class MacUIFactory implements UIFactory {
createButton(): Button {
return new MacButton();
}
createCheckbox(): Checkbox {
return new MacCheckbox();
}
}
// Использование
function createUI(factory: UIFactory) {
const button = factory.createButton();
const checkbox = factory.createCheckbox();
console.log(button.render());
console.log(checkbox.render());
button.onClick();
checkbox.onCheck();
}
const windowsFactory = new WindowsUIFactory();
const macFactory = new MacUIFactory();
createUI(windowsFactory);
createUI(macFactory);
Когда использовать
Используйте Abstract Factory, когда:
- Система должна быть независимой от способа создания объектов
- Нужно создавать семейства связанных объектов
- Продукты должны использоваться вместе и должны быть совместимы
- Хотите предоставить библиотеку продуктов и раскрыть только их интерфейсы
Преимущества
- Гарантирует совместимость создаваемых продуктов
- Избегает тесной связанности между конкретными классами и клиентским кодом
- Принцип единственной ответственности: создание продуктов вынесено в одно место
- Принцип открытости/закрытости: легко добавлять новые семейства продуктов
Недостатки
- Сложность кода из-за множества дополнительных интерфейсов и классов
- Сложность добавления новых типов продуктов (нужно изменять все фабрики)
Отличия от других паттернов
- Factory Method — создает один тип продукта
- Builder — создает сложные объекты пошагово
- Prototype — создает объекты путем клонирования
Реальный пример
// Фабрика для создания элементов интерфейса разных тем
class ThemeFactory {
createButton() {
throw new Error('Метод createButton должен быть переопределен');
}
createInput() {
throw new Error('Метод createInput должен быть переопределен');
}
createModal() {
throw new Error('Метод createModal должен быть переопределен');
}
}
class DarkThemeFactory extends ThemeFactory {
createButton() {
return new DarkButton();
}
createInput() {
return new DarkInput();
}
createModal() {
return new DarkModal();
}
}
class LightThemeFactory extends ThemeFactory {
createButton() {
return new LightButton();
}
createInput() {
return new LightInput();
}
createModal() {
return new LightModal();
}
}
// Использование
function createForm(themeFactory) {
const button = themeFactory.createButton();
const input = themeFactory.createInput();
const modal = themeFactory.createModal();
return { button, input, modal };
}
const darkForm = createForm(new DarkThemeFactory());
const lightForm = createForm(new LightThemeFactory());
Abstract Factory — это ключ к созданию согласованных семейств объектов!