Niespodziewana zmiana stanu obiektu jest jedną z częstszych przyczyn błędów programistów. Dwoma, moim zdaniem, najczęstszymi przypadkami, w których zmiana może być zaskoczeniem są:
- Zmiana stanu obiektu gdy jest on przekazany do metody, zwłaszcza funkcji, której nazwa sugeruje że jest to bezpieczne
- Zmiana stanu w wyniku współbieżnego dostępu do jednego obiektu
Z pierwszym przypadkiem można sobie poradzić przez bardzo restrykcyjne pilnowanie innych członków zespołu na przeglądach kodu, to rozwiązanie zawodzi gdy korzystamy z zewnętrznych bibliotek.
Drugi problem nie występuje do puki nie współdzielimy obiektów między wątkami. Z taką sytuacją mamy do czynienia na początku rozwoju każdej aplikacji. Wystarczy jednak wprowadzić dodatkowy wątek (np. przez użycie niektórych metod asynchronicznych), albo dodać statyczne pole do kontrolera aby się na niego narazić.
Rozwiązaniem obu problemów są obiekty niezmienne, czyli takie, czyli takie których pola nie ulegają zmianie po utworzeniu. W C# i Javie aby stworzyć klasę, której instancje będą niezmienne należy zadeklarować wszystkie pola jako prywatne, ustawić je w konstruktorze i nie udostępnić żadnej metody je modyfikujące.
public class Address
{
private readonly string addressLine1;
private readonly string addressLine2;
private readonly string zipCode;
private readonly string city;
private readonly string stateArea;
private readonly string country;
public Address(string addressLine1, string addressLine2, string zipCode, string city, string stateArea, string country)
{
this.addressLine1 = addressLine1;
this.addressLine2 = addressLine2;
this.zipCode = zipCode;
this.city = city;
this.stateArea = stateArea;
this.country = country;
}
public string AddressLine1 { get { return addressLine1; } }
public string AddressLine2 { get { return addressLine2; } }
public string ZipCode { get { return zipCode; } }
public string City { get { return city; } }
public string StateArea { get { return stateArea; } }
public string Country { get { return country; } }
}
Problemy językowe
Obiekty niezmienne są jednak słabo wspierane przez C# i Javę.
Pierwszym problemem są pola opcjonalne, domyślnie konstruktor musi ustawić wszystkie, więc albo użytkownik takiego obiektu musi przekazać wartości domyślne do takiego obiektu.
Jeżeli obiekt ma wiele pól to konstruktor staje się długi, a pomyłki związane z przekazaniem wartości do złego pola trudno zauważalne. Poniższy kod zawiera błąd, jednak wcale nie wygląda podejrzanie:
var address = new Address (
"Konwaliowa 31/20",
"20036",
"Lublin",
"",
"",
"");
Jakimś rozwiązaniem jest zastosowanie przeciążeń konstruktora i wartości domyślnych dla jego parametrów, to rozwiązanie sprawdza się gdy klasa ma do 3-4 opcjonalnych pól, przy większej ilości szybko prowadzi do eksplozji kombinatorycznej ilości konstruktorów.
Problem staje się jeszcze bardziej dotkliwy gdy pojawia się potrzeba „zmodyfikowania” takiego obiektu, ponieważ obiektu nie da się zmienić, należy utworzyć nowy obiekt i przekazać do niego zarówno wartości zmienionych pól jak i tych, które zostają bez zmian.
var templateAddress = new Address(
"Konwaliowa 31/20",
"",
"20036",
"Lublin",
"Lubelskie",
"Polska");
var modifedAddress = new Address(
"Konwaliowa 31/21",
templateAddress.AddressLine2,
templateAddress.ZipCode,
templateAddress.City,
templateAddress.StateArea,
templateAddress.Country);
Jedna odpowiedź do “Niezmienny obiekt i budowniczy”