Blog webdeveloperski Patryk yarpo Jar

Problemy z referencjami – czy umiesz w immutable?

Autor wiadomości Marzec 27, 2017

Poniższy wpis ma pokazać jak pisać klasy, których pola są referencjami na inne obiekty, w taki sposób, aby ich używanie było bezpieczne.

Czego potrzebujesz:

  • podstawowych umiejętności kodowania w Javie
  • 5 minut

Obiekt immutable

Jest to obiekt, który raz stworzony zawsze pozostaje taki sam. Pozwala to na tworzenie kodu bardziej odpornego na błędy dzięki zmniejszeniu złożoności kodu i zależności między komponentami.

Prosta klasa z referencjami na inne obiekty

Załóżmy, że mamy klasę z referencjami na inne obiekty. Na potrzeby tego posta będę rozwijał taki model:

public class Person {

    private String name;
    private Set<Role> roles;

    public Person(String name, Set<Role> roles) {
        this.name = name;
        this.roles = roles;
    }
    public String getName() {
        return name;
    }
    public boolean is(Role role) {
        return roles.contains(role);
    }
}

Dodajmy do niej jakiś prosty test jednostkowy (z wykorzystaniem jUnit):

public class PersonTest {

    private static final String NAME = "John";
    private static final Set<Role> ROLES = new HashSet<Role>(Arrays.asList(Role.ADMIN));

    @Test
    public void canInstantiateValidObject() {

        Person person = new Person(NAME, ROLES);

        assertTrue(person.is(Role.ADMIN));
        assertEquals(NAME, person.getName());
    }
}

Na pierwszy rzut oka wszystko wyglada dobrze. Co jednak, jeśli zrobimy coś takiego:

@Test
public void problemWithReference() {

    Set<Role> myRoles = new HashSet<Role>(Arrays.asList(Role.USER));
    Person person = new Person(NAME, myRoles);

    assertTrue(person.is(Role.USER)); // ok

    myRoles.remove(Role.USER);

    assertTrue(person.is(Role.USER)); // błąd
}

Co się stało i dlaczego jest błąd?

Otóż, ponieważ w naszej klasie przechowujemy tylko referencje na obiekty, każda zmiana zmiennej, którą przekazaliśmy do konstruktora będzie widoczna wewnątrz obiektu (chyba, że przez zmianę rozumielibyśmy przypisanie całkowicie innego obiektu - zmiana referencji).

Jak temu zaradzić?

Musimy się upewnić, ze klasa Person nie przechowuje referencji do przekazanego obiektu, a swoją własną kopie. Dzięki temu wartość ta stanie się niezmienialna w sposób niezamierzony przez nas - immutable. Można to osiągnąc np. w taki sposób (zmieniam tylko konstruktor, reszta kodu taka sama):

public Person(String name, Set<Role> roles) {
    this.name = name;
    this.roles = Collections.unmodifiableSet(roles);
}

Dzięki takiemu zabiegowi test jednostkowy, który chwilę temu był zepsuty będzie przechodził. Nasz nowo utworzony obiekt będzie miał własną kopie ról i stanie się niezależny od zewnętrznych operacji. Jest to lepsze rozwiązanie, które być może zjada odrobinę więcej pamięci, ale za to znacznie upraszcza zależności w kodzie i poszukiwanie błędów.

To jest oczywiście wierzchołek góry lodowej wszystkich problemów związanych z przekazywaniem i przechowywaniem wartości / referencji w obiektach. O innych napiszę następnym razem 🙂

 

Komentarze (0) Trackbacks (0)

Brak komentarzy.


Leave a comment

 

Brak trackbacków.