Funkcje pomocnicze w konwencji OOP

0

Cześć.

Staram się napisać program zgodny z paradygmatem obiektowym. Piszę go w Lazarusie. Potrzebowałem, logować pewne rzeczy to pliku. Utworzyłem zatem prywatną metodę logującą w klasie gdzie to było konieczne. Nie jest to nic skomplikowanego, tylko metoda która dopisuje do TMemoryStream kolejne linie i zapisuje na dysku. TMemoryStream jest inicjowany w konstruktorze klasy, w której się zawiera. W zasadzie klasa ta jest singletonem, więc to jest raczej poprawne. Kwestia tego, że obecnie mam potrzebę w kolejnej klasie coś tam logować, i teraz mam problem jak to rozwiązać. Nie chce tworzyć metod globalnych, bo z doświadczenia z pracy wiem, że to rozwiązania problematyczne w późniejszym utrzymaniu, jak i niezgodne z OOP. W takim razie mogę stworzyć kolejną klasę, będącą singletonem, tylko po to, żeby mieć tam tą metodę coś jak TTools. Tylko, że to niewiele różni się od funkcji globalnej. Czy może stworzyć wspólną klasę bazową, dla wszystkich obiektów, które potrzebują logowania i mieć tam metodę logującą i obiekt statyczny typu TMemoryStream ?? W jaki sposób rozwiązujecie podobne kwestie i jak jest poprawnie ??

1

Nie nie nie nie. Absolutnie nie zadne klasy bazowe. Co ci ludzie maja z tym dziedziczeniem... Dodając metodę logującą już złamałeś Zasadę Jednej Odpowiedzialności ;] Kompozycja a nie dziedziczenie! Czy Twoja klasa XYZ jest loggerem? Czy ma korzystać z loggera? Skoro to drugie, to nie masz dziedziczyc tylko komponować!

Powinieneś mieć N-gletonową (no bo możesz przecież w aplikacji generować kilka różnych logów) klasę Logger która zajmuje się logowaniem a sam obiekt tej klasy wyciągać sobie z kontenera IoC / z Service Locatora / z Singletona. Czyli twoje klasy maja mieć pole w klasie które będzie obiektem Logger i za jego pomocą logować.
Można też kombinować z AOP ale nie przesadzałbym na początek.

1

Nie wiem jak to wygląda w dużych projektach (np. komercyjnych), ale osobiście robię to na różne sposoby; Najczęściej albo korzystam z zewnętrznej klasy służącej do logowania i/lub zakreślam w dyrektywy kompilatora kawałki kodu, które wykonują logowanie informacji; Dzięki temu mam jedną klasę służącą do tworzenia logów, którą aktywuję odpowiednim {$DEFINE} lub deaktywuję poprzez usunięcie tej definicji; Poza tym usuwając taką dyrektywę, wykluczam kompilacje kodu logującego i dodanie go do pliku wykonywalnego w momencie, gdy logowanie nie jest mi już potrzebne; A jak znów potrzebne mi są logi, to dopisuję odpowiednią dyrektywę (lub zdejmuję z niej prefiks komentarza) i tyle - mam już logi w plikach;

Tyle że dla mnie ważniejsze od standardów i przyjętych zasad jest to, czy dla mnie jest to wygodne w użyciu i szybkie w modyfikacji (włączenie/wyłączenie, ewentualnie przerobienie końcowej formy logów);

Pasowałoby wiedzieć, czy te logi są Ci potrzebne jedynie do debugowania kodu, czy logowanie ma być także możliwe w trybie release; Jeśli tylko do debugowania, to forma raczej nie ma znaczenia - ważne aby dało się tego przyjemnie używać i łatwo odpiąć, jeśli już logi nie będą potrzebne;

Zobacz sobie jak to wygląda w modułach LCLProc i LazLogger - do logów przewidziany jest zestaw klas oraz globalnych procedur i funkcji; Tyle że tam znowu jest inne podejście, bo instrukcje dodające pojedyncze logi opatrzone są komentarzami; Ale niech wypowiedzą się jeszcze użytkownicy bardziej doświadczeni ode mnie, dlatego że ja jako hobbysta mogę nawet robić jaja w kodzie, co niekoniecznie było by dobrymi praktykami w zespole :]

0

@furious programming w normalnych językach zamiast takich define stosuje się poziomy logowania ;) Pisząc do loggera wybierasz poziom logowania (np. info, debug, warning, error) a oprócz tego w jakimś pliku konfiguracyjnym aplikacji masz do ustawienia poziom logownania który cię interesuje i z takim poziomem utworzone będą obiekty klasy Logger. Jak wybierzesz np. error to w logach będą lądować tylko logi z error. Jak ustawisz jakiś poziom None to logi zostaną puste a wszystkie wywołania loggera w aplikacji będą "puste".

0

Nie wiem jak to wygląda w "normalnych" językach, dlatego że nie znam ich w wystarczającym stopniu, aby cokolwiek sensownego o nich napisać :]

Pisząc pod Lazarusem potrzebowałem coś wymyślić, aby logowanie było w miarę sensowne i wygodne w obsłudze; Mnie osobiście podoba się i wygodnie obsługuje klasę loggera, połączoną z dyrektywami {$DEFINE} i {$IFDEF}; Oczywiście ma to swoje minusy, ale nigdy nie głowiłem się nad tym, w jaki sposób robi się to "profesjonalnie" w tym języku.

0

Takie define mają spore minusy:

  • brak możliwości logowania wielopoziomowego, nie zawsze chcesz widzieć wszystkie komunikaty
  • brak możliwości zmiany logowania po kompilacji!
0

Ok, więc @Shalom ropzumiem, że sugerujesz w n-klasach w których chce logować, mieć prywatne pole będące obiektem loggera. Rozumiem, jednak jeżeli mam dajmy na to 15 obiektów 3 klas, a chcę, żeby zrzucały log do tego samego pliku to powinienem kopiować, po prostu wskaźniki na jeden obiekt loggera, który otwiera plik ? Dodatkowo kiedy go tworzyć ? Przy tworzeniu pierwszego obiektu wymagajacego logowania ? Kiedy go usuwać? Wprowadzić jakiś licznik, ile obiektów używa jeszcze tego loggera ? Gorzej - jak przy tworzeniu nowego obiektu przypisywać wartość do wskaźnika ? Bo przekazywanie w konstruktorze moim zdaniem słabe, a trzymanie gdzieś globalnego wskaźnika na obiekt logujący znów zakrawa o zwykłą globalną funkcję. Przy polu statycznym klasy mam pewność, że nikt mi nie zabije za wcześnie loggera i że będzie taki sam dla wszystkich obiektów klasy. Troszkę nie rozumiem jak miałbym zrealizować podaną przez Ciebie kompozycje.

0

@prixans zamiast zadawać głupie pytania przeczytaj coś na temat rozwiązań które podałem wyżej:

  • Singleton
  • Service Locator
  • Kontener IoC
    Każde z tych rozwiazań odpowiada na twoje pytania.
0

Hmm, dziękuję za opinie. Niemniej odczuwam pewną niechęć, czy wręcz odsyłanie na siłę gdzie indziej. Pytanie nie były głupie, a konkretnie pytały o kontrolę obiektów loggerów. Jak sam napisałeś : Singleton - mój pierwotny pomysł z TTools, Service Locator - pomysł z wskaźnikiem na loger w każdym obiekcie, z tym, że zamiast liczników wewnątrz loggera mamy SL, natomiast IoC to nic innego jak to co wyżej, jednak zamiast obiekt wołać wskaźnik, kładzie go kontener do obiektu ... Masz racje, odpowiedzi były w tych hasłach, jednak nie były głupie, głupie by były jakby poruszały kwestie nieistotne, niezwiązane z problemem. Niemniej dziękuję wszystkim za opinie, teraz wiem, że moje pomysły nie były złe, zupełnie poza oop, tylko wymagały dopieszczenia. Przetestuję wszystkie sposoby, zainteresowały mnie te 3 wzorce i z pewnością dla nauki je zaimplementuje. Postaram się tez zaimplementować rozwiązanie @furious programming bo uważam, że ciekawa jest możliwość sterowania makrami logowaniem, jednak możliwość ustalenia poziomu logowania z pliku konfiguracyjego jest fajne, bo w obecnych czasach byt kodu logującego nie boli nas przy obecnych maszynach. Z tym, że jednak czasami faktycznie nie chce dawać pewnych logów do wersji release.

Jeszcze raz dziekuję za uwagę i idę testować.

0

Postaram się tez zaimplementować rozwiązanie @furious programming bo uważam, że ciekawa jest możliwość sterowania makrami logowaniem [...]

Ciekawa może i jest, ale w dużych projektach czy przy dużych wymaganiach odnośnie systemu logowania, to rozwiązanie może się okazać nieporęczne lub niewygodne w obsłudze;

[...] jednak możliwość ustalenia poziomu logowania z pliku konfiguracyjego jest fajne, bo w obecnych czasach byt kodu logującego nie boli nas przy obecnych maszynach. Z tym, że jednak czasami faktycznie nie chce dawać pewnych logów do wersji release.

Możesz wszystko zrobić - łącznie ze zmianą trybu logowania podczas działania programu; Ale to na pewno nie będzie tak wygodne i "fajne", jak w przypadku innych języków, w których funkcjonalność prowadzenia logów jest wbudowana;

Dyrektywami kompilatora możesz obejmować kawałki kodu, ale one nie zostaną skompilowane, więc i nie trafią do pliku wykonywalnego; Dlatego wszelkie zmiany muszą być ustalone już na poziomie kompilacji; Aby można było zmieniać tryb logowania podczas działania programu - musisz wszystko zaprogramować w kodzie aplikacji, dorobić tego obsługę i skompilować, wiec dyrektywy odpadają;

Jest to możliwe, ale na pewno nie tak wygodne i poręczne, jak w innych językach i środowiskach; Ale tak jak napisałem - to raczej domowe rozwiązanie, przeznaczone dla własnych-niewielkich projektów, a nie szeroko stosowana technika; W przypadku Lazarusa można to rozwiązać na różne sposoby; Ja wzorowałbym się na istniejącym w jego źródłach systemie logowania, dlatego że Lazarus i FPC to duże projekty, w których trzeba korzystać z czegoś sensownego; Zobacz po źródłach biblioteki standardowej oraz do modułu LazLogger - tam znajduje się implementacja loggera (niecała).

0

Z pewnością przeanalizuje LazLoggera, jednak po studium wspomnianych wzorców projektowych sądzę, że nie ma żadnych przeszkód, żeby zastosować je w Lazarusie. Może wywiąże się z tego ciekawy projekt ;)

1 użytkowników online, w tym zalogowanych: 0, gości: 1