Альтернативы передаче флага в метод? - PullRequest
23 голосов
/ 24 мая 2011

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

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 может быть лучше выбор, чем bool, но я думаю, что мой вопрос все еще применим в этом случае.

Ответы [ 6 ]

28 голосов
/ 24 мая 2011

Существует не только одно решение для такого рода проблем.

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

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

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

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

Метод разделения : Другое решение - иногда разделить метод на несколько закрытых и предоставить два или более открытых метода.

Объект контекста : отсутствие именованных параметров в C ++ и 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);

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

Анти-узоры

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

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

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

 foo( x, true );  

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

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

 foo( x, UseHigherAccuracy );

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

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

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

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

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

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

10 голосов
/ 24 мая 2011

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

4 голосов
/ 24 мая 2011

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

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

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

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

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

0 голосов
/ 15 января 2019

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

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 как параметр функции и предпочитаю помещать в структуру, даже если мне придется вызывать

myClass->enableCondition();

0 голосов
/ 24 мая 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...