Как избежать дублирования кода при расширении старой функциональности?

Мне нужен совет, как лучше разделить логику между классами. Или какой паттерн в данной ситуации лучше всего использовать.

У меня есть метод, который умеет отправлять имейл, только одному получателю. Типов имейла два - системные (SYSTEM) и персональные (NON_SYSTEM) Для системного типа также важно, чтобы appletName не был пустым.

Вот как выглядит этот метод в контроллере.

Controller:

async send(
    @Body() body: SendSystemMessageDto | SendNonSystemMessageDto,
    @User() user: User,
    @Applet() appletName: string,
  ) {
    let sendResponse = null;

    const emailSendPayload = {
      ...body,
      ...body.details,
    };

    if (appletName && body.type === MESSAGE_TYPE.SYSTEM) {
      sendResponse = await this.emailSystemSendService.send({
        uid: appletName, 
        ...emailSendPayload 
      });
    } else if (body.type === MESSAGE_TYPE.NON_SYSTEM) {
      sendResponse = await this.emailPersonalSendService.send({ 
        uid: String(user.uid), 
        ...emailSendPayload 
      });
    }

    return { success: true, ...sendResponse };
}

Сейчас передо мной стоит задача добавить возможность отправлять имейл для множества получателей.

Для этого, из тела запроса будет доставаться параметр recipientsAddresses и перебираться в цикле. Для каждого имейл адреса, будет вызываться код, что описан выше.

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

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

В совокупности, что надо делать

  1. Выбрать тип письма - SYSTEM or NON_SYSTEM
  2. Выбрать тип отправки (на один имейл или на множество)
  3. Если множественная отправка И тип имейла NON_SYSTEM - перед тем как процессить отправку в цикле, нужно вызвать метод getStatistics для пользователя, который хочет сделать рассылку.

Бизнес логика у меня реализована полностью.

Проблема с тем, как мне красиво реализовать выбор типа письма и метод его обработки (в цикле или нет).

Я думала сначала вынести выбор типа письма в отдельную стратегию. Но есть проблема с формированием входного обЪекта для каждой стратегии. Значение uid разное для метода send каждой сервиса по отправке имейлов.

Затем создать файл MessageProccessorService, которому передать стратегию и тело запроса.

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

То есть после определения количества получателей, мне нужно делать снова проверку на тип имейла.

То есть от стратегии толку не особо. Логика проверки типа имейла дублируется.

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

Буду благодарна за ваши советы.


Ответы (2 шт):

Автор решения: santavital

На мой взгляд проблема тут не в разделении логики, а в сохранении (сейчас объясню)
Если есть обычный алгоритм описанный русским-разговорным языком, те самые 1, 2, 3, то и следует переложить этот алгоритм на конкретный язык программирования.
Даже если это окажется не супер эффективно, главное что сохраняется человеческая логика и программа ТОЧНО делает то, что мы от нее ожидаем.
Если же мы подвергаем 1,2,3 оптимизации неизбежно возникает (так называемый) семантический разрыв. Это когда мы ожидаем от программы 1,2,3 но под капотом оптимизированная программа делает "уже никто и не помнит" что-именно ?!

→ Ссылка
Автор решения: qwabra

Сейчас передо мной стоит задача добавить возможность отправлять имейл для множества юзеров.

один из вариантов:

Не смотря на то, что этот вариант может показаться избыточным я рекомендую в нём разобраться.

  1. Он простой для понимания.
  2. Здесь избыточность находится под большим вопросом.

    (т.к. $body скорее всего объект)



    а ещё, иногда, можно пожертвовать производительностью в угоду удобочитаемости

  3. Вы можете этот способ переиначить на свой лад.

    Например можно просто сделать так: user: $user | $user[], чтобы не плодить классы, функции ...
    там песочница с примером

class Mail {
    send(
        body: $body,
        user: $user,
        appletName: $appletName,
    ) {
        // TODO: -,-
    }
}
class MailExt extends Mail {
    send2many(arr: Array<Parameters<Mail['send']>>) {
        arr.forEach(sendProps => this.send(...sendProps))
    }
}
t1: {
    const body: $body = null as any
    const appletName: $appletName = null as any
    const user: $user = null as any

    const mail = new Mail

    mail.send(
        body, user, appletName
    )
}

t2: {
    const body: $body = null as any
    const appletName: $appletName = null as any
    const users: $user[] = null as any

    const mail = new MailExt

    mail.send2many(users.map(user =>
        [body, user, appletName]
    ))
}
type SendSystemMessageDto = {} // FIXME: -,-
type SendNonSystemMessageDto = {} // FIXME: -,-
type $body = SendSystemMessageDto | SendNonSystemMessageDto
type $user = {} // FIXME: -,-
type $appletName = string
→ Ссылка