Myślę, że to pytanie powinno dotyczyć większości programów, które ładują ustawienia z pliku. Moje pytanie jest z punktu widzenia programowania i naprawdę chodzi o to, jak poradzić sobie z ładowaniem ustawień z pliku pod względem różnych klas i dostępności. Na przykład:
- Jeśli program ma prosty
settings.ini
plik, czy jego zawartość powinna zostać załadowana doload()
metody klasy, a może konstruktora? - Czy wartości powinny być przechowywane w
public static
zmiennych, czy też powinny istniećstatic
metody uzyskiwania i ustawiania właściwości? - Co powinno się stać, jeśli plik nie istnieje lub nie można go odczytać? Jak poinformowałbyś resztę programu, że nie może uzyskać tych właściwości?
- itp.
Mam nadzieję, że pytam o to we właściwym miejscu. Chciałem, aby pytanie było jak najbardziej agnostyczne, ale koncentruję się głównie na językach, które mają takie dziedziczenie - zwłaszcza Java i C # .NET.
Odpowiedzi:
To jest naprawdę bardzo ważne pytanie i często jest robione źle, ponieważ nie ma dostatecznego znaczenia, mimo że jest to podstawowa część prawie każdej aplikacji. Oto moje wytyczne:
Twoja klasa konfiguracji, która zawiera wszystkie ustawienia, powinna być zwykłym starym typem danych, struct / class:
Nie powinno to wymagać metod i nie powinno obejmować dziedziczenia (chyba że jest to jedyny wybór w twoim języku dla implementacji pola odmiany - patrz następny akapit). Może i powinien używać kompozycji do grupowania ustawień w mniejsze określone klasy konfiguracji (np. SubConfig powyżej). Jeśli zrobisz to w ten sposób, idealnie będzie przekazywać testy jednostkowe i ogólnie aplikację, ponieważ będzie miała minimalne zależności.
Prawdopodobnie będziesz musiał użyć typów wariantów, w przypadku gdy konfiguracje dla różnych konfiguracji są niejednorodne w strukturze. Przyjmuje się, że będziesz musiał włączyć dynamiczny rzut w pewnym momencie, gdy czytasz wartość, aby rzucić ją na właściwą (pod-) klasę konfiguracji, i bez wątpienia będzie to zależało od innego ustawienia konfiguracji.
Nie powinieneś być leniwy, wpisując wszystkie ustawienia jako pola, wykonując następujące czynności:
Jest to kuszące, ponieważ oznacza, że możesz napisać uogólnioną klasę serializacji, która nie musi wiedzieć, z jakimi polami ma do czynienia, ale jest błędna i wyjaśnię to za chwilę.
Serializacja konfiguracji odbywa się w całkowicie oddzielnej klasie. Niezależnie od tego, jakiego interfejsu API lub biblioteki użyjesz do tego, treść funkcji serializacji powinna zawierać wpisy, które w zasadzie oznaczają mapę od ścieżki / klucza w pliku do pola na obiekcie. Niektóre języki zapewniają dobrą introspekcję i mogą to zrobić dla Ciebie od razu po wyjęciu z pudełka, inne musisz jawnie napisać mapowanie, ale najważniejsze jest, aby napisać mapowanie tylko raz. Weźmy na przykład ten fragment, który zaadaptowałem z dokumentacji parsera opcji programu zwiększającego c ++:
Zauważ, że ostatni wiersz w zasadzie mówi „optymalizacja” odwzorowuje na Config :: opt, a także, że istnieje deklaracja typu, którego oczekujesz. Chcesz, aby odczyt konfiguracji nie powiódł się, jeśli typ nie jest zgodny z oczekiwaniami, jeśli parametr w pliku nie jest tak naprawdę zmiennoprzecinkowy lub int, albo nie istnieje. Tzn. Błąd powinien wystąpić podczas odczytu pliku, ponieważ problem dotyczy formatu / sprawdzania poprawności pliku i należy zgłosić kod wyjątku / powrotu i zgłosić dokładny problem. Nie należy opóźniać tego później w programie. Dlatego nie powinieneś kusić się, aby złapać cały styl słownika Conf, jak wspomniano powyżej, który nie zawiedzie, gdy plik zostanie odczytany - ponieważ rzutowanie jest opóźnione, dopóki wartość nie jest potrzebna.
Powinieneś uczynić klasę Config tylko do odczytu w pewien sposób - ustawiając zawartość klasy raz podczas jej tworzenia i inicjowania z pliku. Jeśli potrzebujesz w swojej aplikacji ustawień dynamicznych, które się zmieniają, a także stałych, które tego nie robią, powinieneś mieć osobną klasę do obsługi tych dynamicznych, zamiast starać się, aby bity twojej klasy konfiguracji nie były tylko do odczytu .
Idealnie czytasz w pliku w jednym miejscu programu, tzn. Masz tylko jedno wystąpienie „
ConfigReader
”. Jeśli jednak masz problem z przekazaniem instancji Config tam, gdzie jest ona potrzebna, lepiej mieć drugi ConfigReader niż wprowadzić konfigurację globalną (co, jak sądzę, jest tym, co OP rozumie przez „statyczny” ”), co prowadzi mnie do następnego punktu:Unikaj uwodzicielskiej syreny singletona: „Uratuję cię, abyś musiał omijać tę klasę, wszyscy twoi konstruktorzy będą piękni i czyści. Dalej, to będzie takie proste”. Prawda ma dobrze zaprojektowaną, testowalną architekturę, w której prawie nie trzeba przekazywać klasy Config lub jej części przez tak wiele klas aplikacji. To, co znajdziesz, w klasie najwyższego poziomu, funkcji main () lub cokolwiek innego, rozwiążesz conf na poszczególne wartości, które podasz klasom komponentów jako argumenty, które następnie złożysz ponownie (zależność ręczna iniekcja). Pojedynczy / globalny / statyczny conf sprawi, że testowanie aplikacji będzie znacznie trudniejsze do wdrożenia i zrozumienia - np. Wprowadzi w błąd nowych programistów w zespole, którzy nie będą wiedzieć, że muszą ustawić stan globalny, aby przetestować różne rzeczy.
Jeśli twój język obsługuje właściwości, powinieneś ich użyć do tego celu. Powodem jest to, że bardzo łatwo będzie dodać „wyprowadzone” ustawienia konfiguracji, które zależą od jednego lub więcej innych ustawień. na przykład
Jeśli Twój język natywnie nie obsługuje idiomu właściwości, może to obejść, aby osiągnąć ten sam efekt, lub po prostu utworzysz klasę opakowania, która zapewnia ustawienia premii. Jeśli nie możesz w inny sposób nadać korzyści właściwościom, w innym przypadku strata czasu polega na ręcznym pisaniu i korzystaniu z getters / setters po prostu w celu zadowolenia jakiegoś boga OO. Lepiej będzie z prostym, starym polem.
Może być potrzebny system do łączenia i pobierania wielu konfiguracji z różnych miejsc w kolejności pierwszeństwa. Ta kolejność pierwszeństwa powinna być dobrze zdefiniowana i zrozumiała dla wszystkich programistów / użytkowników, np. Rozważ rejestr Windows HKEY_CURRENT_USER / HKEY_LOCAL_MACHINE. Powinieneś zrobić ten funkcjonalny styl, abyś mógł zachować swoje konfiguracje tylko do odczytu, tj .:
zamiast:
Powinienem na koniec dodać, że oczywiście, jeśli wybrany framework / język zapewnia własne wbudowane, dobrze znane mechanizmy konfiguracji, powinieneś rozważyć korzyści z korzystania z niego zamiast z rozwijania własnego.
Więc. Wiele aspektów do rozważenia - popraw to, a to głęboko wpłynie na architekturę aplikacji, redukując błędy, ułatwiając testowanie i zmuszając cię do użycia dobrego projektu w innym miejscu.
źródło
.ini
, aby był czytelny dla człowieka, ale czy sugerujesz, żebym powinien serializować klasę ze zmiennymi w?Ogólnie (moim zdaniem) najlepiej pozwolić aplikacji radzić sobie ze sposobem przechowywania konfiguracji i przekazać konfigurację do modułów. Umożliwia to elastyczność w jaki sposób ustawienia są zapisywane tak, że można kierować pliki lub webservices lub baz danych lub ...
Dodatkowo nakłada na aplikację ciężar „co się stanie, gdy coś się nie powiedzie”, kto najlepiej wie, co oznacza ta awaria.
A to sprawia, że ton łatwiejsze do testów jednostkowych, kiedy można po prostu przejść w obiekcie konfiguracyjnym zamiast dotykać systemu plików lub radzić sobie z kwestiami współbieżności wprowadzonych dostępu statycznego.
źródło
Jeśli używasz .NET do programowania klas, masz różne opcje, takie jak Zasoby, web.config, a nawet plik niestandardowy.
Jeśli korzystasz z Resources lub web.config, dane są faktycznie przechowywane w pliku XML, ale ładowanie jest szybsze.
Pobieranie danych z tych plików i przechowywanie ich w innej lokalizacji będzie przypominało podwójne użycie pamięci, ponieważ są one domyślnie ładowane do pamięci.
W przypadku każdego innego pliku lub języka programowania zadziała powyższa odpowiedź Benedykta.
źródło