当两个功能非常相似时如何避免复制和粘贴?

How to avoid copy and paste when two functions are very similar?

我经常遇到具有相同结构和逻辑的方法,但有一些差异,我找不到不重复自己的正确方法。

例如:

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);
}

调用者负责实现仿函数。有没有没有这个问题的替代方案?

您可以将两个函数合并为一个带有附加条件参数的函数,例如:

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() 会调用它。

您不必使参数化 selectChannels public。它可以是 selectAlmostOkChannelsselectNotOkChannels 的私有实现细节,您的 public 函数。

selectChannels甚至可以实现为函数模板,这样生成的代码就相当于hand-written"copy pasted"版本,没有代码重复的维护负担

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 */;
    });
}

您可能被告知模板需要在 headers 中,但这实际上不是全部!模板定义需要在实例化的地方可见。由于您的模板仅用于成员函数的私有实现,因此您的模板定义可以在实现两个成员函数的同一个文件中