Każda większa aplikacja potrzebuje zapisywać sobie jakieś ustawienia. Aby to ułatwić wymyśliłem interfejs IConfiguration
. Stwierdziłem, że niezależnie od implementacji tego interfejsu, będzie potrzebowali dwóch metod:
T GetProperty<T>(string propertyName);
void SetProperty<T>(string propertyName, T propertyValue);
Dodatkowo, aby uprościć dostęp do wszystkich ustawień na raz, interfejs posiada metodę
IEnumerable<KeyValuePair<string, object>> GetAllProperties();
Przykładową implementacją takiej konfiguracji będzie klasa posiadająca prywatne pola/własności, która za pomocą instrukcji switch
będzie zwracała/przypisywała odpowiednie wartości, sprawdziwszy poprawność typu T
z typem własności.
PropertyBasedConfiguration
Aby ułatwić sobie tworzenie klas konfiguracji (i ponieważ miałem problemy z deserializacją zagnieżdżonych kolekcji) stworzyłem abstrakcyjną klasę PropertyBasedConfiguration
, która za pomocą refleksji odwołuje się do swoich własności. Utworzenie nowej klasy konfiguracji jest w tym momencie banalne. Wystarczy utworzyć klasę, która dziedziczy po PropertyBasedConfiguration
i dodać potrzebne jej własności, a metody zaimplementowane przez bazę robią resztę.
Implementację możecie zobaczyć tutaj
Zapisywanie konfiguracji
Żeby mieć wolną rękę co do formatu plików konfiguracji (json, xml, yaml, plik binarny, itp.) utworzyłem interfejs IConfigurationFormat
, który wymusza implementację następujących metod:
void WriteConfiguration(IConfiguration data, Stream stream);
T ReadConfiguration<T>(Stream stream) where T : IConfiguration, new();
BinaryConfigurationFormat
Jeśli nie potrzebujemy edytować naszej konfiguracji spoza programu możemy zapisać dane w czysto binarnej formie. Taką implementację jest BinaryConfigurationFormat
, gdzie używam obiektu BinaryFormatter
do jednoznacznego zapisywania danych do strumienia. Kod.
YamlConfigurationFormat
Z drugiej strony jeśli chcemy mieć plik konfiguracji bardziej przyjazny dla oka ludzkiego posłużymy się np. YAMLem. I o ile BinaryFormatter
bezproblemowo działał na dowolnych obiektach, zapisując ich typ, to YamlDotNet, którego używam, nie był w stanie domyślić się czym miał być object
. Początkowo udało mi się zrobić system rzutowania dla typów podstawowych, ale dla kolekcji to nie działało, bardzo. Więc zmieniłem podejście. Napisałem PropertyBasedConfiguration
i ograniczyłem YamlConfigurationFormat
do jej pochodnych. Przez to mogę mieć (w dużym stopniu) pewność silnego typowania podczas deserializacji. Jeśli ktoś będzie używał typu object
wiedząc, że jest to int
, musi się liczyć z deserializacją do string
a. Kod.