Лучше взять аргумент объекта или использовать объект-член? - PullRequest
5 голосов
/ 29 октября 2010

У меня есть класс, который я могу написать так:

class FileNameLoader
{
     public:
         virtual bool LoadFileNames(PluginLoader&) = 0;
         virtual ~FileNameLoader(){}
};

Или это:

class FileNameLoader
{
     public:
         virtual bool LoadFileNames(PluginLoader&, Logger&) = 0;
         virtual ~FileNameLoader(){}
};

Первый предполагает, что в реализации FileNameLoader есть член Logger&. Второй нет. Тем не менее, у меня есть некоторые классы, которые имеют много методов, которые внутренне используют Logger. Поэтому второй метод заставил бы меня написать больше кода в этом случае. Logger на данный момент является синглтоном. Я предполагаю, что так и останется. Что из них «красивее» и почему? Какова обычная практика?

EDIT: Что если этот класс не был назван Logger? :). У меня есть Builder также. Как насчет тогда?

Ответы [ 6 ]

7 голосов
/ 29 октября 2010

Я не вижу, какое дополнительное преимущество у подхода два по сравнению с одним (даже с учетом модульного тестирования!), Фактически с двумя, вы должны убедиться, что везде, где вы вызываете конкретный метод, доступен Logger для передачи - и чтоможет усложнить ситуацию ...

Когда вы создаете объект с помощью регистратора, вы действительно видите необходимость его изменить?Если нет, зачем беспокоиться о втором подходе?

5 голосов
/ 29 октября 2010

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

3 голосов
/ 29 октября 2010

Прежде всего следует убедиться, что зависимость Logger предоставляется пользователем в любом случае. Предположительно, в первом случае конструктор для FileNameLoader принимает параметр Logger&?

Ни в коем случае я ни при каких обстоятельствах не сделаю Регистратор Синглтоном. Никогда, никогда, ни как, ни как. Это либо внедренная зависимость, либо у вас есть функция Log free, либо, если вам абсолютно необходимо, использовать глобальную ссылку на объект std::ostream в качестве универсального регистратора по умолчанию. Класс Singleton Logger - это способ создания препятствий для тестирования без какой-либо практической выгоды. Так что, если какая-то программа создаст два объекта Logger? Почему это даже плохо, не говоря уже о том, чтобы создавать проблемы для себя, чтобы предотвратить? Первое, что я делаю в любой сложной системе ведения журналов, - это создание PrefixLogger, который реализует интерфейс Logger, но печатает указанную строку в начале всех сообщений, чтобы показать некоторый контекст. Singleton несовместим с такой динамической гибкостью.

Во-вторых, спросите, не захотят ли пользователи иметь один FileNameLoader, и вызовите LoadFileNames несколько раз, с одним регистратором в первый раз и другим регистратором во второй раз.

Если это так, то вам определенно нужен параметр Logger для вызова функции, потому что средство доступа для изменения текущего Logger (а) не является отличным API и (б) невозможно со ссылочным членом в любом случае: вы бы получили изменить на указатель. Возможно, вы могли бы сделать параметр регистратора указателем со значением по умолчанию 0, хотя значение 0 означает «использовать переменную-член». Это позволит использовать, когда пользовательский код начальной настройки знает и заботится о ведении журнала, но затем этот код передает объект FileNameLoader другому коду, который будет вызывать LoadFileNames, но не знает или не заботится о ведении журнала.

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

[Изменить относительно Строителя: я думаю, что вы можете найти и заменить в моем ответе, и он все еще остается в силе. Принципиальное различие заключается в том, является ли «Строитель, используемый этим объектом FileNameLoader» инвариантным для данного объекта, или же «Строитель, используемый в вызове» - это то, что звонящие в LoadFileNames должны настраивать для каждого вызова отдельно.

Возможно, я немного менее непреклонен, что Строитель не должен быть Синглтоном. Слегка. Могущество.]

1 голос
/ 29 октября 2010

Я бы придерживался первого метода и использовал бы Logger как синглтон.Различные приемники и определение того, откуда данные были записаны, - это общая проблема .Определение раковины может быть простым или сложным, как вы хотите.Например (при условии, что Singleton <> является базовым классом для синглетонов в вашем коде):

class Logger : public Singleton<Logger>
{
public:
    void Log(const std::string& _sink, const std::string& _data);
};

Ваш класс:

class FileNameLoader
{
 public:
     virtual bool LoadFileNames(PluginLoader& _pluginLoader)
     {
         Logger.getSingleton().Log("FileNameLoader", "loading xyz");
     };

     virtual ~FileNameLoader(){}
};

Вы можете иметь по своей сути сложный менеджер журналов с различнымираковины, разные лог-уровни, разные выходы.Ваш метод Log () в менеджере журналов должен поддерживать простое ведение журнала, как описано выше, и тогда вы можете использовать более сложные примеры.Например, для целей отладки вы можете определить разные выходные данные для разных приемников, а также иметь объединенный журнал.

1 голос
/ 29 октября 2010

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

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

Вы также можете применить Закон Деметры в качестве аргументапротив предоставления экземпляра протоколирования при вызове функции.

Конечно, будут определенные моменты, когда это не подходит.Общими примерами могут быть:

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

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

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

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