Что лучше: группа получателей или 1 метод с параметром строки выбора?

15

Наша область знаний включает людей, которые ходят по пластине для записи давления босыми ногами. Мы выполняем распознавание изображений, в результате чего получаются объекты класса «Foot», если в данных датчика распознается нога человека.

Есть несколько расчетов, которые должны быть выполнены на данных ноги.

Теперь, какой API будет лучше:

class Foot : public RecognizedObject  { 
  MaxPressureFrame getMaxPressureFrame();
  FootAxis getFootAxis();
  AnatomicalZones getAnatomicalZones();

  // + similar getters for other calculations

  // ...
}

Или:

class Foot : public RecognizedObject {
  virtual CalculationBase getCalculation(QString aName);

  // ...
}

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

Любой совет?

Некоторые про за первый подход могут быть:

  • ПОЦЕЛУЙ - все очень конкретно. API, но реализация также.
  • строго типизированные возвращаемые значения.
  • наследование от этого класса является надежным. Ничто не может быть переопределено, только добавлено.
  • API очень закрыт, ничего не входит, ничто не может быть переопределено, поэтому меньше может пойти не так.

Некоторые минусы:

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

Некоторые плюсы для второго подхода:

  • более гибкий
  • API менее вероятно изменится (при условии, что мы получили правильную абстракцию, в противном случае изменение будет стоить дороже)

Некоторые минусы:

  • слабо набранный. Необходимо использовать каждый звонок.
  • строковый параметр - у меня плохое предчувствие (ветвление строковых значений ...)
  • В настоящее время не существует варианта использования / требования, которое требовало бы дополнительной гибкости, но это может произойти в будущем.
  • API накладывает ограничения: каждый расчет должен быть производным от базового класса. Этот метод будет вынужден выполнять вычисления, и передача дополнительных параметров будет невозможна, если мы не разработаем еще более динамичный, сверхгибкий способ передачи параметров, который еще больше увеличивает сложность.
Bgie
источник
5
Вы можете сделать enumи включить его значения. Тем не менее, я думаю, что второй вариант злой, потому что он отклоняется от KISS.
Vorac
Сложнее найти все применения конкретного вычисления, если вы используете строковый параметр для его запуска. Трудно быть на 100%, вы нашли их всех.
Конрад Моравский
Возьмите лучшее из двух миров и напишите фасад с привлечением множества получателей getCalculation().
Вскоре
1
Перечисления определенно лучше, чем строки! Я не думал об этом. Они ограничивают API и предотвращают злоупотребление строковым параметром (например, concat of tokens и прочее дерьмо). Поэтому я полагаю, что сравнение выполняется между опцией 1 и опцией 2 с enum вместо string.
Bgie

Ответы:

6

Я думаю, что в первом подходе окупится. Волшебные строки могут создавать следующие проблемы: ошибки ввода, неправильное использование, нетривиальная безопасность возвращаемого типа, отсутствие завершения кода, неясный код (была ли эта версия в этой функции? Я думаю, мы узнаем об этом во время выполнения). Использование enums решит некоторые из этих проблем, но давайте посмотрим на минусы, которые вы подняли:

  • Количество получателей будет расти, так как каждый новый расчет добавляется в список

Правда, это может раздражать, однако, это делает вещи красивыми и строгими, и дает вам завершение кода в любом месте вашего проекта в любой современной IDE, с хорошими заголовочными комментариями, которые гораздо более полезны, чем перечисления.

  • API, скорее всего, изменится, и если будут внесены критические изменения, нам понадобится новая версия API, Foot2.

Да, но на самом деле это огромный профессионал;) вы можете определить интерфейсы для частичных API, и тогда вам не нужно перекомпилировать зависимый класс, на который не влияют более новые API (поэтому нет необходимости в Foot2). Это обеспечивает лучшую развязку, теперь зависимость от интерфейса, а не от реализации. Более того, если существующий интерфейс изменится, у вас будет ошибка компиляции в зависимых классах, что отлично подходит для предотвращения устаревания кода.

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

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

user116462
источник
Мне нравится идея частичного API с интерфейсами, она кажется перспективной. Я пойду с этим. Спасибо. Если это станет слишком загроможденным (foot реализует слишком много интерфейсов), использование нескольких небольших классов адаптеров будет еще более гибким: если существует несколько вариантов foot с разными API (например, foot, humanfoot, dogfoot, humanfootVersion2), может быть маленький адаптер для каждого, чтобы разрешить одному виджету GUI работать со всеми из них ...
Bgie
Преимущество использования селектора команд состоит в том, что можно иметь реализацию, которая получает команду, которую она не понимает, вызывая статический вспомогательный метод, предоставляемый с интерфейсом. Если команда представляет собой что-то, что может быть выполнено почти во всех реализациях с использованием подхода общего назначения, но что некоторые реализации могут быть в состоянии сделать с помощью лучших средств [рассмотрим, например, IEnumerable<T>.Count], такой подход может позволить коду воспользоваться преимуществами производительности новых особенности интерфейса при использовании реализаций, которые поддерживают их, но остаются совместимыми со старыми реализациями.
суперкат
12

Я бы порекомендовал вариант 3: дать понять, что вычисления не являются неотъемлемой частью абстракции a Foot, а работают с ней. Затем вы можете разделить Footвычисления и на отдельные классы, например так:

class Foot : public RecognizedObject {
public:
    // Rather low-level API to access all characteristics that might be needed by a calculation
};

class MaxPressureFrame {
public:
    MaxPressureFrame(const Foot& aFoot); // Performs the calculation based on the information in aFoot
    //API for accessing the results of the calculation
};

// Similar classes for other calculations

Таким образом, вы по-прежнему строго печатаете свои расчеты и можете добавлять новые, не влияя на существующий код (если только вы не допустили серьезную ошибку в объеме информации, предоставленной Foot).

Барт ван Инген Шенау
источник
Определенно лучше, чем варианты 1 и 2. Это всего лишь короткий шаг от использования шаблона разработки стратегии.
Брайан
4
Это может быть уместно. Это может быть излишним. Зависит от того, насколько сложны расчеты. Вы собираетесь добавить MaxPressureFrameStrategy и MaxPressureFrameStrategyFactory? И это имеет тенденцию превращать Ногу в Анемический Объект.
user949300
Хотя это хорошая альтернатива, обратная зависимость не будет работать в нашем случае. Нога также должна выступать в качестве своего рода посредника, поскольку некоторые вычисления должны быть пересчитаны, если другой изменился (из-за изменения пользователем параметра или около того).
Bgie
@Bgie: С такими зависимостями между вычислениями я согласен с @ user116462, что первый вариант самый лучший.
Барт ван Инген Шенау