Как избежать копирования и вставки, когда две функции очень похожи? - PullRequest
6 голосов
/ 23 сентября 2019

Я часто сталкиваюсь с методами, которые имеют одинаковую структуру и логику, но с некоторыми отличиями, и я не нахожу правильный способ не повторяться.

Например:

void ChannelSelection::selectAlmostOkChannels(int currentInkId)
{
    bool selected = true;
    foreach (auto report, m_reports) {
        if (report.scoreByInk.find(currentInkId) != report.scoreByInk.end()) {
            auto tmpStatus = Assessment::getStatusFromScore(report.scoreByInk.value(currentInkId));
            if (tmpStatus == Assessment::Ok)
                selected = false;
            else if (tmpStatus == Assessment::NotOk)
                m_autoSelection[report.name].setSelected(currentInkId, false);
        }
    }
    m_currentSelection.insert(currentInkId, selected);
}

void ChannelSelection::selectNotOkChannels(int currentInkId)
{
    bool selected = true;
    foreach (auto report, m_reports) {
        if (report.scoreByInk.find(currentInkId) != report.scoreByInk.end()) {
            auto tmpStatus = Assessment::getStatusFromScore(report.scoreByInk.value(currentInkId));
            if (tmpStatus == Assessment::Ok || tmpStatus == Assessment::AlmostOk)
                selected = false;
        }
    }
    m_currentSelection.insert(currentInkId, selected);
}

Как видите, эти две функции очень похожи (отличается только внутренняя функция).Как я могу красиво удалить дублирование в этом коде?

Одним из решений, о котором я подумал, является использование функтора, что-то вроде:

void ChannelSelection::selectChannels(int currentInkId, std::function<bool()> fn)
{
    bool selected = true;
    foreach (auto report, m_reports) {
        if (report.scoreByInk.find(currentInkId) != report.scoreByInk.end()) {
            auto tmpStatus = Assessment::getStatusFromScore(report.scoreByInk.value(currentInkId));
            selected = fn();
        }
    }
    m_currentSelection.insert(currentInkId, selected);
}

Вызывающая сторона получает ответственность за реализацию функтора.Есть ли альтернатива без этой проблемы?

Ответы [ 2 ]

5 голосов
/ 23 сентября 2019

Вам не нужно делать ваши параметризованные selectChannels общедоступными.Это может быть частная реализация ваших общих функций selectAlmostOkChannels и selectNotOkChannels.

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

template<typename SelectFunction>
void ChannelSelection::selectChannels(int currentInkId, SelectFunction selectFn)
{
    bool selected = true;
    foreach (auto report, m_reports) {
        if (report.scoreByInk.find(currentInkId) != report.scoreByInk.end()) {
            auto tmpStatus = Assessment::getStatusFromScore(report.scoreByInk.value(currentInkId));
            selected = selectFn(tmpStatus);
            /* fill in */
        }
    }
    m_currentSelection.insert(currentInkId, selected);
}

void ChannelSelection::selectAlmostOkChannels(int currentInkId)
{
    selectChannels(currentInkId, [] (auto tmpStatus) -> bool {
        return /* fill in */;
    });
}

void ChannelSelection::selectNotOkChannels(int currentInkId)
{
    selectChannels(currentInkId, [] (auto tmpStatus) -> bool {
        return /* fill in */;
    });
}

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

1 голос
/ 23 сентября 2019

Вы можете объединить две функции в одну с дополнительным условным параметром, например:

void ChannelSelection::selectChannels(int currentInkId, bool condition)
{
  bool selected = true;
  foreach (auto report, m_reports) {
    if (report.scoreByInk.find(currentInkId) != report.scoreByInk.end()) {
      auto tmpStatus = Assessment::getStatusFromScore(report.scoreByInk.value(currentInkId));
      if (condition) {
        if (tmpStatus == Assessment::Ok) {
          selected = false;
        } else if (tmpStatus == Assessment::NotOk) {
          m_autoSelection[report.name].setSelected(currentInkId, false);
        }
      } else if (tmpStatus == Assessment::Ok || tmpStatus == Assessment::AlmostOk) {
        selected = false;
      }
    }
  }
  m_currentSelection.insert(currentInkId, selected);
}

Вызов его с помощью condition == true вызовет эквивалент функции selectAlmostOkChannels(), а в противном случае selectNotOkChannels()1007 *

...