Rejestratory nazywamy „problemem przekrojowym”. Ulegają technikom takim jak programowanie zorientowane na aspekt; jeśli masz sposób ozdobić swoje klasy atrybutem lub wykonać trochę tkania kodu, to jest to dobry sposób na uzyskanie możliwości rejestrowania przy jednoczesnym zachowaniu czystości obiektów i list parametrów.
Jedynym powodem, dla którego możesz chcieć przekazać program rejestrujący jest to, że chcesz określić różne implementacje rejestrowania, ale większość platform rejestrowania ma elastyczność pozwalającą na konfigurowanie ich, powiedzmy, dla różnych celów rejestrowania. (plik dziennika, Menedżer zdarzeń systemu Windows itp.)
Z tych powodów wolę, aby logowanie było naturalną częścią systemu, niż przekazywanie programu rejestrującego do każdej klasy w celu logowania. Więc ogólnie robię to odwoływanie się do odpowiedniej przestrzeni nazw rejestrowania i po prostu używanie rejestratora w moich klasach.
Jeśli nadal chcesz przekazać rejestrator, preferuję, aby uczynić go ostatnim parametrem na liście parametrów (jeśli to możliwe, ustaw parametr opcjonalny). Posiadanie pierwszego parametru nie ma większego sensu; pierwszy parametr powinien być najważniejszy, najbardziej odpowiedni dla działania klasy.
W językach z przeciążeniem funkcji argumentowałbym, że im bardziej prawdopodobne jest, że argument będzie opcjonalny, tym bardziej powinno być. Zapewnia to spójność podczas tworzenia przeciążeń tam, gdzie ich brakuje:
W językach funkcjonalnych odwrotność jest bardziej przydatna - im bardziej prawdopodobne jest, że wybierzesz jakieś domyślne, tym bardziej powinno być. Ułatwia to specjalizację funkcji, po prostu stosując do niej argumenty:
Jednak, jak wspomniano w innych odpowiedziach, prawdopodobnie nie chcesz jawnie przekazywać programu rejestrującego na liście argumentów każdej klasy w systemie.
źródło
Z pewnością możesz poświęcić dużo czasu na nadmierne zaprojektowanie tego problemu.
W przypadku języków z implementacjami rejestrowania kanonicznego wystarczy utworzyć instancję rejestratora kanonicznego bezpośrednio w każdej klasie.
W przypadku języków bez implementacji kanonicznej spróbuj znaleźć strukturę elewacji rejestrującej i trzymaj się jej. slf4j to dobry wybór w Javie.
Osobiście wolałbym trzymać się jednej konkretnej implementacji rejestrowania i wysyłać wszystko do syslog. Wszystkie dobre narzędzia do analizy dzienników są w stanie połączyć dzienniki sysout z wielu serwerów aplikacji w kompleksowy raport.
Gdy podpis funkcji zawiera jedną lub dwie usługi zależności, a także niektóre „rzeczywiste” argumenty, umieszczam zależności na końcu:
int calculateFooBarSum(int foo, int bar, IntegerSummationService svc)
Ponieważ moje systemy mają zwykle tylko pięć lub mniej takich usług, zawsze upewniam się, że usługi są zawarte w tej samej kolejności we wszystkich podpisach funkcji. Porządek alfabetyczny jest tak dobry, jak każdy inny. (Poza tym: utrzymanie tego metodologicznego podejścia do obsługi muteksów również zmniejszy twoje szanse na zakleszczenie).
Jeśli stwierdzisz, że wstrzykujesz kilkanaście zależności w swojej aplikacji, prawdopodobnie system musi zostać podzielony na osobne podsystemy (czy mogę powiedzieć, że mikrousług?).
źródło
Rejestratory są trochę szczególnym przypadkiem, ponieważ muszą być dostępne dosłownie wszędzie.
Jeśli zdecydowałeś, że chcesz przekazać program rejestrujący do konstruktora każdej klasy, zdecydowanie powinieneś ustalić spójną konwencję dotyczącą tego, jak to robisz (np. Zawsze pierwszy parametr, zawsze przekazywany przez referencję, zawsze zaczyna się lista inicjująca konstruktora z m_logger (theLogger) itp.). Wszystko, co będzie używane w całej bazie kodu, kiedyś zyska na spójności.
Alternatywnie, możesz kazać każdej klasie utworzyć własny obiekt rejestratora, bez potrzeby przekazywania czegokolwiek. Rejestrator może potrzebować kilku „magicznych” informacji, aby zadziałało, ale zakodowanie ścieżki pliku w definicji klasy jest potencjalnie o wiele łatwiejsze w utrzymaniu i mniej żmudne niż przekazywanie go poprawnie setkom różnych klas, i zapewne o wiele mniej złego niż używanie globalnej zmiennej do ominięcia tego nudy. (Trzeba przyznać, że rejestratory należą do nielicznych uzasadnionych przypadków użycia zmiennych globalnych)
źródło
Zgadzam się z tymi, którzy sugerują, że dostęp do programu rejestrującego powinien być statyczny, a nie przekazywany do klas. Jednakże jeśli istnieje silny powód chcesz przekazać go w (być może różne instancje chcą zalogować się do różnych miejsc, czy coś), to proponuję nie zdać go przy użyciu konstruktora ale raczej zrobić osobną rozmowę, aby to zrobić, np
Class* C = new C(); C->SetLogger(logger);
raczej niżClass* C = new C(logger);
Powodem preferowania tej metody jest to, że program rejestrujący nie jest zasadniczo częścią klasy, ale raczej funkcją wstrzykiwaną wykorzystywaną do innych celów. Umieszczenie go na liście konstruktorów powoduje, że jest to wymóg klasy i implikuje, że jest on częścią faktycznego stanu logicznego klasy. Uzasadnione jest na przykład oczekiwanie (w przypadku większości klas, choć nie wszystkich), że jeśli
X != Y
tak,C(X) != C(Y)
to jest mało prawdopodobne, aby przetestować nierówność programu rejestrującego, jeśli porównujesz zbyt instancje tej samej klasy.źródło
Warto wspomnieć, że nie widziałem tutaj innych odpowiedzi, że zmuszenie rejestratora do wstrzykiwania za pośrednictwem właściwości lub właściwości statycznych utrudnia (e) jednostkowe przetestowanie klasy. Na przykład, jeśli twój rejestrator jest wstrzykiwany poprzez właściwość, będziesz musiał wstrzykiwać ten rejestrator za każdym razem, gdy testujesz metodę wykorzystującą rejestrator. Oznacza to, że równie dobrze możesz ustawić to jako zależność konstruktora, ponieważ klasa tego wymaga.
Static nadaje się do tego samego problemu; jeśli program rejestrujący nie działa, oznacza to, że cała klasa nie działa (jeśli klasa korzysta z programu rejestrującego) - nawet jeśli program rejestrujący niekoniecznie jest „częścią” odpowiedzialności klasy - chociaż nie jest tak zły jak zastrzyk własności, ponieważ przynajmniej wiedz, że rejestrator jest zawsze „tam” w pewnym sensie.
Tylko trochę do przemyślenia, zwłaszcza jeśli stosujesz TDD. Moim zdaniem program rejestrujący naprawdę nie powinien być częścią testowalnej części klasy (podczas testowania klasy nie powinieneś również testować logowania).
źródło
Jestem zbyt leniwy, aby przekazywać obiekt rejestrujący do każdej instancji klasy. W moim kodzie tego rodzaju rzeczy albo siedzą w polu statycznym, albo w zmiennej lokalnej wątku w polu statycznym. To drugie jest całkiem fajne i pozwala na użycie innego rejestratora dla każdego wątku oraz pozwala dodawać metody włączania i wyłączania logowania, które robią coś sensownego i oczekiwanego w aplikacji wielowątkowej.
źródło