Абстрактная фабрика
Суть паттерна "Абстрактная фабрика" и его реализация на TypeScript
Абстрактная фабрика является логическим продолжением паттерна Фабричный метод. Поэтому я использую тот же пример, что и для фабричного метода, но с небольшим изменением.
Задача. Есть некая логика асинхронной актуализации данных, которая очень важна для приложения. После её выполнения мы сразу уведомляем юзера о том, что данные успешно обновлены.
Проблема. Бизнес-логика едина, но... тут будет основное отличие
В статье про фабричный метод было условие
реализация уведомлений для разных платформ разная
А сейчас усложним условие:
реализация различных утилитарных вещей на этих платформах разная. В частности, реализация уведомлений.
т.е. предполагается что для платформ различается не только отправка уведомлений, но и какие-то другие действия и нужно под это заложиться. Например, в зависимости от платформы мы будем по-разному работать с локальным хранилищем.
Шаг 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();
}
}
Комментарии