Wraz z rozwojem mojej gry, rozwijały się także moje klasy, oraz funkcjonalności jakie oferowały, oraz potrzebowały. Gdy stanąłem naprzeciw problemu zrzucenia plansz, oraz stanu gry na dysk twardy (bez sensu, by za każdym razem wszystko w pamięć generować), postanowiłem, że będzie to realizowane za pomocą serializacji do pliku XML, ponieważ sam format uważam za niejako standard, oraz myślę o ciekawym pomyśle na rozszerzenie mojej gry, a nie wiem z jakiej technologii, ten pomysł będzie korzystał (.NET, albo PHP myślę), więc XML wydaję się być idealnym rozwiązaniem.
Całe zakodowanie klasy (przygotowanie do serializacji, oraz utworzenie odpowiednich metod) przebiegało bardzo łatwo i przyjemnie. Pierwszy raz korzystałem z serializacji do XML pod .NET. Oczywiście pojawił się problem: nie można zserializować tablicy interfejsów. Przeszukałem Internet w celu znalezienia jakiegoś sensownego rozwiązania, ale jak się okazało na różnorakich blogach powtarzała się jedna myśl: „to nie będzie ładne rozwiązania”. Jak się okazuje programiści różnie podchodzą do tego problemu. Możemy pobawić się w dziedziczenie, możemy napisać samodzielnie serializacje i deserializacje, albo możemy napisać klasę stanu instancji interfejsu. Osobiście preferuje napisać stany dla tych interfejsów. Dlaczego? Ponieważ wydaję się, że dzięki temu mamy większą kontrolę nad tym co jest serializowane, oraz jak obiekty są deserializowane, a nie musimy pisać dla całej klasy, która posiada ową tablicę interfejsów. Serializując same stany możemy zserializować nawet pola prywatne klas implementujących ten interfejs. Zademonstruję to na przykładzie.
using System;
using System.Xml.Serialization;public interface IBoo
{
void pobierzStan();
stanIBoo zaladujStan();
}
[Serializable]
public class stanIBoo
{
public string nazwa;
public int liczba;
public stanIBoo()
{
}
public stanIBoo(int _liczba, string _nazwa)
{
this.liczba = _liczba;
this.nazwa = _nazwa;
}
}
public class BooBoo : IBoo
{
public string nazwa;
private int liczba;
void pobierzStan(stanIBoo _stan)
{
this.nazwa = _stan.nazwa;
this.liczba = _stan.liczba;
}
stanIBoo zaladujStan()
{
return new stanIBoo(this.liczba, this.nazwa);
}
}
public class BooFoo : IBoo
{
private string nazwa;
private int liczba;
void pobierzStan(stanIBoo _stan)
{
this.nazwa = _stan.nazwa;
this.liczba = _stan.liczba;
}
stanIBoo zaladujStan()
{
return new stanIBoo(this.liczba, this.nazwa);
}
}
[Serializable]
public class Foo
{
public int x;
public int y;
private string nazwa;
[XmlIgnore]
public IBoo[] twarze;
public stanIBoo[] stany;
public void OnSerialization()
{
/*…kod…*/
}
public void OnDeserialized()
{
/*…kod…*/
}
}
Więc mamy klasę, która trzyma wszystko (czyli, Foo), w tym przypadku ma ona jedynie dwie metody OnSerialization(), która musi być wywoływana, przed serializacją, oraz OnDeserialized(), która jest wywoływana po serializacji. Można się oczywiście pobawić i poszukać w dokumentacji sposobu jak to zrobić automatycznie (podpowiem: [OnSerialization], [OnDeserialized]), ale nie to jest najważniejsze. OnSerialization() tworzy tablicę stanów (stany) i ją zapełnia odpowiednimi stanami (czytaj: pobiera stan z kolejno z każdej instancji interfejsu i zapisuje go do tablicy), później serializuje, później czyści tablicę stanów (stany), by zwolnić pamięć. OnDeserialized deserializuje całą klasę, w tym tablicę stanów, później wczytuje stany i czyści tablicę stanów. Oczywiście musi, albo implementować interfejs ISerializable, albo posiadać atrybut [Serializable] i być publiczną klasą.
Mamy interfejs, który każe implementować dwie metody, jedną która zwraca stan i drugą, która go pobiera.
Mamy dwie klasy, które implementują wyżej wymieniony interfejs.
Trik polega na tym, że tablica interfejsów ma atrybut [XmlIgnore], albo jest prywatna, przez co nie jest serializowana.
Wiem, że rozwiązanie jest bardziej czasochłonne niżeli byśmy serializowali same instancje tych klas, ale wydaje mi się bardziej eleganckie, a dodatkowo daję większą kontrolę nad procesem serializacji.