Подтвердить что ты не робот

Альтернативы передаче флага в метод?

Иногда при исправлении дефекта в существующей кодовой базе я могу (часто из лени) принимать решение об изменении метода из:

void
MyClass::foo(uint32_t aBar)
{
    // Do something with aBar...
}

в

void
MyClass::foo(uint32_t aBar, bool aSomeCondition)
{
    if (aSomeCondition)
    {
        // Do something with aBar...
    }
}

Во время обзора кода коллега упомянул, что лучший подход будет заключаться в подклассе MyClass для предоставления этой специализированной функциональности.

Однако я бы сказал, что до тех пор, пока aSomeCondition не нарушает цель или сплоченность MyClass, это приемлемый шаблон. Только в том случае, если код был внедрен с флагами, а операторы if - наследование было бы лучшим вариантом, иначе мы потенциально могли бы войти в архитектуру территории астронавта.

Что здесь может быть здесь?

Примечание: Я только что увидел этот связанный ответ, который предполагает, что enum может быть лучше чем a bool, но я думаю, что мой вопрос по-прежнему применяется в этом случае.

4b9b3361

Ответ 1

Существует не только одно решение этой проблемы.

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

Enum - хороший выбор, если дела исключительны. Перечисления могут быть легко перенесены в битовую маску или объект контекста.

Бит-маска: С++ включает язык C, вы можете использовать некоторые простые старые практики. Иногда небольшая маска на unsigned int является хорошим выбором (но вы теряете проверку типов), и вы можете ошибочно ошибочно принять маску. Это удобный способ плавного перехода от логического или аргумента перечисления к этому типу шаблона. Битовая маска может быть перенесена с некоторым усилием на объект-контекст. Возможно, вам придется реализовать какую-то поразрядную арифметику, такую ​​как operator | и operator &, если вам нужно поддерживать совместимость времени сборки.

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

Разбиение метода. Еще одно решение - разделить метод на несколько частных и предоставить два или более общедоступных метода.

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

class Context{
public:
  // usually not a good idea to add public data member but to my opinion this is an exception
  bool setup:1; 
  bool foo:1;
  bool bar:1;
  ...
  Context() : setup(0), foo(0), bar(0) ... {}
};
...    

Context ctx;
ctx.setup = true; ...
MyObj.foo(ctx);

Примечание: Это также полезно для минимизации доступа (или использования) статических данных или запроса к объекту singleton, TLS... Контекстный объект может содержать намного больше данных кэширования, связанных с алгоритмом. ... Я позволяю вашему воображению бежать бесплатно...

Анти-шаблоны

Я добавляю здесь несколько анти-шаблонов (чтобы предотвратить некоторую смену подписи): * НИКОГДА НЕ ДЕЛАЙТЕ ЭТО *

  • * НИКОГДА НЕ ДЕЛАЙТЕ ЭТОГО * используйте статический int/bool для передачи аргументов (некоторые люди делают это, и это кошмар, чтобы удалить этот материал). Перерыв по крайней мере многопоточности...
  • * НИКОГДА НЕ ДЕЛАЙТЕ ЭТО * добавить член данных для передачи параметра в метод.

Ответ 2

К сожалению, я не думаю, что есть четкий ответ на проблему (и это я часто встречаю в своем собственном коде). С булевым:

 foo( x, true );  

вызов трудно понять.

С перечислением:

 foo( x, UseHigherAccuracy );

это легко понять, но вы, как правило, получаете такой код:

 foo( x, something == someval ? UseHigherAccuracy : UseLowerAccuracy );

что вряд ли является улучшением. И с несколькими функциями:

 if ( something == someval ) {
      AccurateFoo( x );
 }
 else {
      InaccurateFoo( x );
 }

у вас получилось намного больше кода. Но я думаю, это проще всего читать, и я бы привык, но мне все еще не нравится: - (

Одна вещь, которую я определенно НЕ делал, однако, является подклассом. Наследование должно быть последним инструментом, который вы когда-либо достигали.

Ответ 3

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

Ответ 4

Общее правило, которое я использую: если aSomeCondition существенно изменяет характер функции, тогда я рассматриваю подклассу.

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

Некоторые примеры:

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

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

Ответ 5

IMHO, флаг aSomeCondition изменяется или зависит от состояния текущего экземпляра, поэтому при определенных условиях этот класс должен изменить свое состояние и обрабатывать указанную операцию по-разному. В этом случае я могу предложить использование State Pattern. Надеюсь, что это поможет.

Ответ 6

Я бы просто изменил код:

void MyClass::foo(uint32_t aBar, bool aSomeCondition)
{
    if (aSomeCondition)
    {
        // Do something with aBar...
    }
}

чтобы:

void MyClass::foo(uint32_t aBar)
{
    if (this->aSomeCondition)
    {
        // Do something with aBar...
    }
}

Я всегда опускаю bool в качестве параметра функции и предпочитаю помещать в struct, даже если бы мне пришлось вызывать

myClass->enableCondition();