Na ogół unikam, aby klasa wiedziała, jak się serializować, z kilku powodów. Po pierwsze, jeśli chcesz (od) serializować do / z innego formatu, musisz teraz zanieczyścić model tą dodatkową logiką. Jeśli dostęp do modelu uzyskuje się przez interfejs, wówczas również zanieczyszczasz umowę.
public class Image
{
public void toJPG(String filePath) { ... }
public Image fromJPG(String filePath) { ... }
}
Ale co, jeśli chcesz serializować to do / z PNG i GIF? Teraz klasa staje się
public class Image
{
public void toJPG(String filePath) { ... }
public Image fromJPG(String filePath) { ... }
public void toPNG(String filePath) { ... }
public Image fromPNG(String filePath) { ... }
public void toGIF(String filePath) { ... }
public Image fromGIF(String filePath) { ... }
}
Zamiast tego zazwyczaj lubię używać wzoru podobnego do następującego:
public interface ImageSerializer
{
void serialize(Image src, Stream outputStream);
Image deserialize(Stream inputStream);
}
public class JPGImageSerializer : ImageSerializer
{
public void serialize(Image src, Stream outputStream) { ... }
public Image deserialize(Stream inputStream) { ... }
}
public class PNGImageSerializer : ImageSerializer
{
public void serialize(Image src, Stream outputStream) { ... }
public Image deserialize(Stream inputStream) { ... }
}
public class GIFImageSerializer : ImageSerializer
{
public void serialize(Image src, Stream outputStream) { ... }
public Image deserialize(Stream inputStream) { ... }
}
W tym momencie jednym z zastrzeżeń dotyczących tego projektu jest to, że serializatory muszą znać identity
obiekt, który serializuje. Niektórzy twierdzą, że jest to zły projekt, ponieważ implementacja wycieka poza klasę. Ryzyko / zysk zależy od ciebie, ale możesz nieco ulepszyć klasy, aby zrobić coś takiego
public class Image
{
public void serializeTo(ImageSerializer serializer, Stream outputStream)
{
serializer.serialize(this.pixelData, outputStream);
}
public void deserializeFrom(ImageSerializer serializer, Stream inputStream)
{
this.pixelData = serializer.deserialize(inputStream);
}
}
Jest to bardziej ogólny przykład, ponieważ obrazy zwykle mają metadane, które są z nimi zgodne; rzeczy takie jak poziom kompresji, przestrzeń kolorów itp., które mogą skomplikować proces.
ImageSerializer
interfejsu)ImageSerializer
interfejs również musi się rozwijać. EX: Nowy format obsługuje opcjonalną kompresję, poprzednie nie -> dodawały konfigurowalność kompresji doImageSerializer
interfejsu. Ale potem inne formaty są zaśmiecone funkcjami, które ich nie dotyczą. Im więcej o tym myślę, tym mniej myślę, że dziedziczenie ma tutaj zastosowanie.void serialize(Image image, Stream outputStream, SerializerSettings settings);
To tylko połączenie istniejącej logiki kompresji i metadanych z nową metodą.Serializacja to problem dwuczęściowy:
O ile to możliwe, konstrukcja powinna być oddzielona od mechaniki . Zwiększa to modułowość twojego systemu. Jeśli zakopiesz informacje na temat nr 2 w swojej klasie, wówczas przerwiesz modułowość, ponieważ teraz twoja klasa musi zostać zmodyfikowana, aby nadążyć za nowymi sposobami serializacji (jeśli się pojawią).
W kontekście serializacji obrazów informacje o serializacji byłyby oddzielone od samej klasy i trzymane raczej w algorytmach, które mogą określać format serializacji - dlatego różne klasy dla JPEG, PNG, BMP itp. Jeśli jutro nowa algorytm serializacji przychodzi po prostu kod tego algorytmu, a umowa klasy pozostaje niezmieniona.
W kontekście IPC możesz rozdzielić klasę, a następnie selektywnie zadeklarować informacje potrzebne do serializacji (za pomocą adnotacji / atrybutów). Następnie algorytm serializacji może zdecydować, czy do serializacji użyć JSON, buforów protokołu Google, czy XML. Może nawet zdecydować, czy użyć parsera Jacksona, czy niestandardowego - istnieje wiele opcji, które można łatwo uzyskać przy projektowaniu modułowym!
źródło