Шаблоны проектирования очень важны для веб-разработчиков, поскольку позволяют повышать качество кода. В этой статье будет представлен шаблон “Цепочка ответственности” с использованием TypeScript.

Шаблон “Цепочка ответственности” (“Цепочка обязанностей”)  —  это способ избежать жесткой зависимости между отправителем и получателем запроса. При этом нескольким объектам предоставляется возможность обрабатывать запрос. В шаблоне “Цепочка ответственности” множество объектов образуют цепочку, в которой каждый объект связан с последующим ссылкой. Запросы передаются по цепочке до тех пор, пока один из объектов в цепочке не решит обработать запрос.

У каждой должности в компании свои обязанности и полномочия. Возьмем в качестве примера процесс взятия отгулов в компании: когда я прошу отгул, он должен быть одобрен только руководителем группы, и нет необходимости передавать его супервайзеру и директору. Если звено в цепочке ответственности не может обработать текущий запрос, то при наличии следующего звена запрос будет передан на обработку этому следующему звену.

В процессе разработки ПО распространенным сценарием применения шаблона “Цепочка ответственности” становится промежуточное программное обеспечение. Посмотрим, как используется шаблон “Цепочка ответственности” для обработки запросов.

Чтобы лучше понять код ниже, взгляните сначала на соответствующую UML-диаграмму:

На приведенном выше изображении определен интерфейс Handler. В этом интерфейсе определены следующие два метода.

  • use(h: Handler): Handler => используется для регистрации обработчика (промежуточного ПО)
  • get(url: string, callback: (data: any) => void): void => регистрация обработчика запросов get.

Интерфейс Handler:

interface Handler {
use(h: Handler): Handler;
get(url: string, callback: (data: any) => void): void;
}

Теперь определим абстрактный класс AbstractHandler, который инкапсулирует логику обработки цепочки ответственности. То есть объединим различные обработчики, чтобы сформировать ссылочную цепочку.

Абстрактный класс AbstractHandler:

abstract class AbstractHandler implements Handler {
  next!: Handler;
  use(h: Handler) {
    this.next = h;
    return this.next;
  }

  get(url: string, callback: (data: any) => void) {
    if (this.next) {
      return this.next.get(url, callback);
    }
  }
}

Основываясь на абстрактном классе AbstractHandler, определяем AuthMiddleware и LoggerMidddleware соответственно. Промежуточное ПО AuthMiddleware используется для обработки аутентификации пользователя, а LoggerMidddleware  —  для вывода журналов запросов.

Класс AuthMiddleware:

class AuthMiddleware extends AbstractHandler {
  isAuthenticated: boolean;
  constructor(username: string, password: string) {
    super();

    this.isAuthenticated = false;
    if (username === "bytefer" && password === "666") {
      this.isAuthenticated = true;
    }
  }

  get(url: string, callback: (data: any) => void) {
    if (this.isAuthenticated) {
      return super.get(url, callback);
    } else {
      throw new Error("Not Authorized");
    }
  }
}

Класс LoggerMiddleware:

class LoggerMiddleware extends AbstractHandler {
get(url: string, callback: (data: any) => void) {
console.log(`Request url is: ${url}`);
return super.get(url, callback);
}
}

С помощью промежуточного ПО AuthMiddleware и LoggerMidddleware определим класс Route для регистрации этих промежуточных программ.

Класс Route:

class Route extends AbstractHandler {
  urlDataMap: { [key: string]: any };
  constructor() {
    super();
    this.urlDataMap = {
      "/api/todos": [
        { title: "Learn Design Pattern" },
      ],
      "/api/random": () => Math.random(),
    };
  }

 get(url: string, callback: (data: any) => void) {
    super.get(url, callback);

  if (this.urlDataMap.hasOwnProperty(url)) {
      const value = this.urlDataMap[url];
      const result = typeof value === "function" ? value() : value;
      callback(result);
    }
  }
}

После определения класса Route можно использовать его и зарегистрировать промежуточные программы следующим образом:

const route = new Route();
route.use(new AuthMiddleware("bytefer", "666"))
 .use(new LoggerMiddleware());

route.get("/api/todos", (data) => {
  console.log(JSON.stringify({ data }, null, 2));
});

route.get("/api/random", (data) => {
  console.log(data);
});

При успешном запуске приведенного выше кода вывод будет таким, как на следующем изображении:

Подведем итого того, как используется шаблон “Цепочка ответственности”.

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

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Bytefer: Design Patterns: Chain of Responsibility Pattern in TypeScript

Предыдущая статьяВажнейшие инструменты и технологии для разработки под Web 3.0
Следующая статьяКак разбить монолитное приложение на микросервисы без рефакторинга