Cześć. Mam kilka obiektów (wszystkie dziedziczą po tej samej klasie, a czasem jeszcze po sobie) w kompozycie.
I teraz chcę móc je zapisywać na kilka sposobów:
- Do bazy danych (SQL i NoSQL)
- Do pliku XML
- Do binarki
itd.
I od razu uprzedzam - jeśli chodzi o bazę danych to zapis ma być w odpowiednich tabelach i kolumnach, a nie coś w stylu XMLColumn :)
Ma być też opcja cofania operacji - tu prawdopodobnie skończy się na pamiątce.
I teraz tak. Mam kilka pomysłów, jak to zrobić i mam też własnego faworyta.
Bardziej mi jednak chodzi o sam sposób zapisu obiektu, niż o wzorzec w stylu CQRS.
No bo tak. Mógłbym stworzyć sobie jakiś interfejs do mapowania obiektu. Coś w stylu:
interface IMyClassMapper
{
void Stream Map(MyClass object);
}
Dalej miałbym konkretne mappery: Do SQL, do XML, i do binarki. Wszystkie zwracałyby jakiś strumień albo w jakiś inny sposób odpowiednio sformatowane dane, dzięki którym wiedziałbym jak to zapisać.
Ale to tworzy pewien poważny problem. Skoro mam mieć zapis do kilku formatów, to muszę stworzyć kilka takich mapperów. A co za tym idzie, każdy mapper musiałby wiedzieć, jak zmapować obiekt. Czyli, jeśli w klasie dojdzie mi jakaś właściwość, to muszę ją dodawać w kilku mapperach. A to już problem.
Mógłbym w prawdzie użyć jednego mappera (właśnie teraz na to wpadłem ;)), który by zwracał coś w rodzaju słownika, gdzie kluczem byłaby nazwa pola, a wartością - wartość ;) Wtedy odpada mi problem jak wyżej. Następnie klasy do zapisu odpowiednio by sobie ten słownik obrabiały. Tak, jest to pewne rozwiązanie.Myślę, że tu też nie byłoby problemu, że zaimplementować pamiątkę. Problem jest za to inny - opisany niżej.
Mam też inny pomysł, który podpatrzyłem kiedyś w systemach Cadowych. W aplikacji, którą kiedyś tworzyłem świetnie się sprawdził. To coś w rodzaju aktywnego rekordu, ale nie do końca.
Chodzi o to, że każdy obiekt ma dwie metody w stylu:
bool DataIn(IObjectData data)
{
base.DataIn(data);
int version = data.Read("version");
//tutaj możemy zareagować odpowiednio do wersji
Name = data.Read("name");
}
bool DataOut(IObjectData data)
{
base.DataOut(data);
data.Write("version", 1);
data.Write("name", Name);
}
Nie ukrywam, że pomysł podoba mi się bardziej. Obiekt nie zapisuje informacji o sobie nigdzie tak naprawdę, więc nie można powiedzieć, że łamie SRP. Można by się wprawdzie czepić, że obiekt pełni w pewien sposób rolę swojego mappera, ale z drugiej strony nie ma pojęcia o tym skąd odczytuje i gdzie zapisuje dane. Ja bym te dwie metody (DataIn i DataOut) traktował bardziej jako coś w rodzaju ToString. Tyle, że nie zwracamy stringa, a zapisujemy dane gdzieś indziej.
Jakie są zalety takiego rozwiązania? Przede wszystkim nie ma mappera. Tutaj klasa wie, jakie ma pola i jakie pola chce zapisywać. I od razu mogę korzystać z dziedziczenia. W momencie, gdy mamy taką sytuację:
class Abstract {}
class Concrete1: Abstract {}
class Concrete2: Abstract{}
class MyClass: Concrete2{}
to problem z Mapperami byłby taki, że musiałbym je dublować (mapper dla Concrete1 i mapper dla MyClass i mapper dla Concrete2, gdzie część pól byłaby taka sama) lub też tworzyć jakieś dziedziczenia mapperów (Mapper dla MyClass dziedziczyłby po Mapperze z Concrete2), co mi już tworzy dodatkową ilość klas dość sztucznie. No bo Mapper nie ma właściwie żadnej funkcjonalności poza tym, że przepisuje dane z jednego obiektu do drugiego. A tyle, ile obiektów do serializacji, tyle mapperów.
Przy rozwiązaniu z moim faworytem takiego problemu nie ma. Również nie ma żadnego kłopotu, żeby stosować pamiątkę. Pamiątka przechowuje obiekt IObjectData.
Więc uważam, że mój faworyt jest lepszym pomysłem niż mapper. Ale wiem, że jest to podobne do ActiveRecord, więc chciałbym zaprosić do dyskusji na temat tego rozwiązania :) Moim zdaniem jest dobre i nie łamie kluczowych zasad. Co o tym sądzicie?