На главную

Абстрактная фабрика

Суть паттерна "Абстрактная фабрика" и его реализация на TypeScript

Абстрактная фабрика является логическим продолжением паттерна Фабричный метод. Поэтому я использую тот же пример, что и для фабричного метода, но с небольшим изменением.

Условия. Допустим, мы пишем приложение которое, должно работать как на веб-страничке так и в качестве расширения для браузеров Chrome и Firefox.

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

Проблема. Бизнес-логика едина, но... тут будет основное отличие

В статье про фабричный метод было условие

реализация уведомлений для разных платформ разная

А сейчас усложним условие:

реализация различных утилитарных вещей на этих платформах разная. В частности, реализация уведомлений.

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

Шаг 1: интерфейсы

Для начала опишем нужные интерфейсы


// типы платформ
type Platform = "firefoxExtension" | "chromeExtension" | "browser";


// класс нотификатора который отвечает за рассылку уведомлений
interface Notificator {
send: (message: string) => void;
}

// класс для работы с хранилищем данных
interface StorageUtils {
put: (key: string, value: any) => void;
pick: (key: string) => any;
}


// класс абстрактной фабрики, которая будет создавать нужные объекты
interface PlatformToolkit {
createNotificator(): Notificator;
createStorageUtils(): StorageUtils;
}

Шаг 2: утилитарные классы и классы абстрактных фабрик

Создадим классы реализующие интерфейсы, описанные в предыдущем шаге. Для каждой из платформ (расширение для Chrome, расширение для Firefox, браузер) создадим своё семейство объектов. При этом опустим детали реализации самих утилитарных методов.


// для расширения Firefox
class FirefoxNotificator implements Notificator {
public send(message: string) {
// ...
}
}

class FirefoxStorage implements StorageUtils {
public put(key: string, value: any) {
// ..
}

public pick(key: string) {
let value = null;
// ..
return value;
}
}

class FirefoxToolKit implements PlatformToolkit {
createNotificator() {
return new FirefoxNotificator();
}

createStorageUtils() {
return new FirefoxStorage();
}
}

// для расширения Chrome
class ChromeNotificator implements Notificator {
public send(message: string) {
// ...
}
}

class ChromeStorage implements StorageUtils {
public put(key: string, value: any) {
// ..
}

public pick(key: string) {
let value = null;
// ..
return value;
}
}

class ChromeToolKit implements PlatformToolkit {
createNotificator() {
return new ChromeNotificator();
}

createStorageUtils() {
return new ChromeStorage();
}
}

// для браузера
class BrowserNotificator implements Notificator {
public send(message: string) {
// ...
}
}

class BrowserStorage implements StorageUtils {
public put(key: string, value: any) {
// ..
}

public pick(key: string) {
let value = null;
// ..
return value;
}
}

class BrowserToolKit implements PlatformToolkit {
createNotificator() {
return new BrowserNotificator();
}

createStorageUtils() {
return new BrowserStorage();
}
}

Шаг 3: используем абстрактную фабрику

Задача осталась прежней: актуализировать данные и отправить уведомление об успешной актуализации.

// теперь `UpdateManager` совсем ничего не знает про создание объектов.
// в конструктор приходит класс из которого можно получить метод работы с уведомлениями
class UpdateManager {
private toolKit: PlatformToolkit;

constructor(toolKit: PlatformToolkit) {
this.toolKit = toolKit;
}

public update() {
fetch("https://my.app.api/update").then(() => {
const notificator = this.toolKit.createNotificator();
notificator.send("Данные успешно обновлены");
});
}
}

class Application {
// при инициализации приложения определяем тулкит для работы с текущей платформой
private toolKit: PlatformToolkit;
private updateManager: UpdateManager;

// знание о платформе по прежнему сосредоточено в корне приложения
constructor(platform: Platform) {
if (platform === "chromeExtension") {
this.toolKit = new ChromeToolKit();
} else if (platform === "firefoxExtension") {
this.toolKit = new FirefoxToolKit();
} else {
this.toolKit = new BrowserToolKit();
}

this.updateManager = new UpdateManager(this.toolKit);
}

public update() {
this.updateManager.update();
}
}

Комментарии