Найти тему
Геннадий Шушпанов

Куда положить интерфейс

Оглавление

Задача

Предположим, что мы загружаем информацию о билетах с сервиса поставщика и хотим отслеживать, какие билеты мы уже загрузили, а какие нет. Определять будем по номеру билета. Для простоты условимся, что вся остальная необходимая информация о билете укладывается в некое "описание".

Агенты

Для решения задачи нам понадобятся:

  • Поставщик. Он будет отвечать за получение билетов.
  • Обработчик. Будет обрабатывать полученные билеты и сохранять их в хранилище.
  • Хранилище. Для хранения полученных билетов Оно же будет отвечать на вопрос: есть уже у нас билет или нет.

Процессор и интерфейсы

Для реализации обработчика создадим класс Processot с методом CheckTickets. Этот метод будет решать свою задачу используя модели билетов (ITicket), поставщика билетов (ITickerPrivider), и хранилища (ITicketStorage).

class Processor {
. ITicketProvider ticketProvider
. ITicketStorage ticketStorage

. void CheckTickets() {
. . foreach(ITicket ticket in ticketProvider.GetTickets()) {
. . . if not ticketStorage.Exists(ticket) {
. . . . ticketStorage.Save(ticket)
. . . }
. . }
. }
}

Код метода позволяет определить интерфейсы для поставщика и хранилища:

interface ITicketProvider {
. ITicket[] GetTickets()
}
interface ITicketStorage {
. boolean Exists(ITicket ticket)
. void Save(ITicket ticket)
}

А модель билета нам подскажет условие задачи:

interface ITicket {
. string Number
. string Info
}

Модули

Так куда положить интерфейсы? В отдельный модуль. Это обеспечит независимость модулей для реализации процессора, поставщика и хранилища друг от друга. А также гибкость в отладке и тестировании. Легко реализовать простой вариант поставщика на массиве и использовать его для отладки кода процессора и хранилища. Или наоборот, реализовать простое хранилище.

Не случайно для билета использован интерфейс, а не класс. Такой подход позволяет каждому агенту иметь свою реализацию интерфейса. Часто тип и структура данных поставщика билетов не совпадет с типом и структурой хранилища. Так, например, реализация поставщика требует обеспечивать десериализацию из JSON, а реализация для хранилища формировать объект данных для записи в реляционной базе данных. И все это может масштабироваться на несколько поставщиков и хранилищ.

class Processor {
. ITicketProvider[] ticketProviders
. Set<IKey, ITicketStorage> ticketStorages

. void CheckTickets() {
. . foreach(ITicketProvider ticketProvider in ticketProviders) {
. . . foreach(ITicket ticket in ticketProvider.GetTickets()) {
. . . . ITicketStorage ticketStorage = ticketStorages[toKey(ticket)]
. . . . if not ticketStorage.Exists(ticket) {
. . . . . ticketStorage.Save(ticket)
. . . . }
. . . }
. . }
. }
}

Для полноты картины скажу еще об одном модуле -- том, который все это будет собирать. Полезно для связей иметь отдельный модуль для сборки, который будет знать обо всех "кубиках". В нем также можно устроить динамическую сборку на основе настроек.

Итак мы получили: модуль для интерфейсов, по модулю для обработчика, поставщика и хранилища и модуль для сборки с простой структурой зависимости: (интерфейсы -> (процессор, поставщик, хранилище)) -> сборщик.

Фильтрация

Задача фильтрации возникает всегда, когда поставщик может ввернуть объектов больше чем нам нужно. Если условие отбора статично, например, нам нужны билеты за последние n дней, то его можно реализовать в классе поставщика задав глубину поиска n в настройках. В этом случае нет необходимости в передаче параметров в GetTickets(). В случае динамического фильтра -- передачи данных, а, следовательно, взаимозависимости не избежать. Тут возможны следующие подходы.

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

Можно прятать запрос в некий справочник с условиями фильтрации и параметрами для них. Взаимозависимости меньше, как и статического контроля за типами данных Заполняем его с учетом всех поставщиков, а они возьмут, то что им предназначено.