c ++: класс logger без глобалов или синглетонов или передачи его каждому методу - PullRequest
20 голосов
/ 04 мая 2011

Кто-нибудь знает, возможно ли иметь класс, подобный регистратору, без:

  • с использованием одиночного или глобального (a la std :: cout)

  • передача экземпляра / указателя / ссылки каждому методу, который в этом нуждается

Я беру пример с классом логгера, но в моем приложении есть несколько классов, которые выиграют от этого (например, менеджер отмены).

Есть несколько проблем с каждым решением:

  • использование синглтона проблематично для тестирования (наряду со многими причинами, как правило, не очень хорошая идея использовать один). То же самое с глобальным. Кроме того, ничто не гарантирует, что в приложении будет только ОДИН экземпляр, и это даже не требование (почему бы не иметь 2 регистраторов, например?)

  • передача его каждому конструктору объектов (внедрение зависимостей) приводит к большому количеству шаблонного кода и может быть подвержена ошибкам, поскольку вам приходится много раз копировать / вставлять один и тот же код. Можно ли серьезно подумать о наличии указателя на Logger в конструкторе каждого отдельного класса ???????

Так что мне было интересно, есть ли третий вариант в C ++, о котором я никогда не слышал? Для меня это звучит так, как будто это потребует некоторой черной магии под капотом, но я был приятно удивлен некоторыми методами, которые я изучил в переполнении стека, которые я не смог найти в Google, поэтому я знаю, что здесь есть настоящие гуру;)

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

Ответы [ 3 ]

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

Я полагаю, вы могли бы сделать что-то похожее на то, что делается в Java с пакетом Log4j (и, вероятно, это делается с его версией Log4c):

Иметь статический метод, который может возвращать несколько экземпляров регистратора:

Logger someLogger = Logger.getLogger("logger.name");

Метод getLogger() не возвращает одноэлементный объект.Он возвращает именованный регистратор (создавая его при необходимости).Logger - это просто интерфейс (в C ++ это может быть полностью абстрактный класс) - вызывающему не нужно знать фактические типы создаваемых Logger объектов.

Вы можете продолжитьимитировать Log4j и иметь перегрузку getLogger(), которая также принимает объект фабрики:

Logger someLogger = Logger.getLogger("logger.name", factory);

Этот вызов будет использовать factory для создания экземпляра регистратора, предоставляя вам больше контроля над тем, что лежит в основе Logger объектовбыли созданы, вероятно, помогая вам издеваться.

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

6 голосов
/ 04 мая 2011

Как насчет класса с некоторыми статическими методами?

class Logger
{
public:
  static void record(string message)
  {
    static ofstream fout("log");
    fout << message << endl;;
  }
  ...
};

...

void someOtherFunctionSomewhere()
{
  Logger::record("some string");
  ...
}

Нет Singleton, нет глобальной переменной, но любой код, который может видеть Logger.h, может вызывать функцию-член, и если все открытые членыфункции возвращают void, тривиально легко заглушить для тестирования.

0 голосов
/ 09 сентября 2013

Я не думаю, что есть хорошая альтернатива в C ++, однако есть одна в Emacs Lisp, о которой стоит подумать: динамическое связывание переменных . Основная концепция выглядит следующим образом: когда вы ссылаетесь на переменную, вы обращаетесь не обязательно к глобальной переменной по имени, но к последней, определенной в пути выполнения с тем же именем. В псевдо-C ++ коде это будет выглядеть так:

// Pseudo-C++ with dynamic binding
Logger logger = Logger("GlobalLogger");

void foo() { logger.log("message"); }

int main() 
{
   foo(); // first
   {
      Logger logger = Logger("MyLogger");
      foo(); // second
   }
   foo(); // third
}

В этом примере псевдо-C ++, когда вы вызываете foo() в первый раз, будет использоваться GlobalLogger, когда вы будете вызывать его второй раз, вместо этого будет вызываться MyLogger, поскольку MyLogger будет переопределять глобальную переменную до тех пор, пока это находится в области действия, даже внутри foo(). Третий вызов вернется к GlobalLogger, поскольку MyLogger выйдет из области видимости. Это позволяет переопределить глобальный регистратор с помощью специального для выбранных фрагментов кода, не пропуская объект регистратора через весь код и не устанавливая глобальную переменную.

Настоящий C ++ не имеет динамического связывания, но должна быть возможность воспроизвести его аспекты:

std::stack<Logger> logger = { Logger("GlobalLogger") };

void foo() { logger.top().log("message"); }

int main()
{
    foo();
    {
      logger.push(Logger("MyLogger"));
      foo();
      logger.pop();
    }
    foo();
}

Для дальнейшей очистки стек должен быть скрыт внутри класса DynamicBinding, ручные операции .push () /. Pop () могут быть скрыты за защитой с ограничением, и могут возникнуть проблемы с многопоточностью, которые необходимо будет принять забота о. Но базовая концепция может работать и давать большую гибкость, чем простая одноэлементная или глобальная переменная.

...