Dzisiaj czas na przykład dość popularnego wzorca projektowego, czyli Obserwatora.

Problem, który będę tutaj opisywał dotyczy przechowywania referencji do obiektu w HashMapie. Obiekt jest dość prosty i zawiera tylko 2 własności, numer i nazwę, oto przykład:


class MyObject {

private String name;

private int number;

}

Żeby nie gmwatwać kodu pominę gettery i settery.

Do przechowywania obiektów wykorzystam równie prosty pojemnik, umożliwiający dodawanie gotowych obiektów i pobieranie ich wg nazwy, klasa nazywa się MyObjectContainer:


class MyObjectContainer {

private Map<String,MyObject>  myObjectsMap = new HashMap<String,MyObject>();

// 2 proste metody do wstawiania i pobierania obiektów

public MyObject get(String key){ // specjalnie nie implementuje interfejsu java.util.Map, bo tam by to nie przeszło

return myObjectsMap.get(key);

}

public MyObject putSimple(MyObject myObject){

return myObjectsMap.put(myObject.getName(), myObject);

}

}

I w ten prosty sposób doszliśmy do problemu, który chciałem opisać, a zdarza się, gdy pobranemu na zewnątrz obiektowi MyObject zostanie zmieniona nazwa. W mapie pozostanie stary wpis, natomiast referencja będzie miała nowa nazwę. Próba pobrania elementu wg. nowej nazwy skończy się albo niesławnym NPE, albo (co gorsza) pobraniem innego elementu, który przypadkiem ma taką sama nazwę. W obydwu przypadkach nie jest za dobrze. Kod ilustrujący znajduje się ponizej:


MyObject myObject = new MyObject("name", 1);

myObjectContainer.putSimple(myObject);

MyObject myObject = myObjectContainer.get( "name" );

myObject.setName( "newName" );

myObjectContainer.get( "newName" );  // i tu jest problem

Jak widać ostatnia linijka oznaczona „tu jest problem”, spowoduje błąd, gdyż obiekt o nazwie „name” zmienił nazwę na „newName” natomiast w mapie pozostanie dostępny jedynie po kluczu „name”.

Żeby temu zapobiec skorzystamy z wzorca obserwator właśnie i w momencie zmiany nazwy obiektu, nasz MyObjectContainer zostanie powiadomiony przez samego zainteresowanego, co da mu możliwość zaktualizownia własnego stanu. Do tego jednak potrzebujemy lekko zmodyfikować nasz przykład. Po pierwsze obiekt MyObject musi mieć możliwość notyfikowania o zmianie swojego stanu, skorzystam tutaj z implementacji Obserwatora /Obserwowanego dostępnej w pakiecie java.util :


class MyObject extends Observable {

public void setName( String newName ) {

String oldName =name;

name = newName;

if( !newName.equals( oldName ) ) {

setChanged();

notifyObservers(oldName);  // przekazemy stara nazwe, zeby dalo sie mape zaktualizowac

}

}

}

Teraz wystarczy tylko dodać możliwości „nasluchiwania” do MyObjectContainer:


class MyObjectContainer implements Observer {

public MyObject putSimple(MyObject myObject){

myObject.addObserver( this );

return myObjectsMap.put(myObject.getName(), myObject);

}

// no i jeszcze aktualizacja stanu

public void update(Observable o, Object arg)  {

String oldName = (String) arg;

MyObject myObject = (MyObject) o;

if( !oldName.equals(myObject.getName())){

MyObject oldObject = myObjectsMap.put(myObject.getName(), myObject);

myObjectsMap.remove(oldName);

if( oldObject != null ) { // to znaczy, ze cos pod tą nazwą już było

}

}

}

Jak widać kontener dostaje starą nazwę dzięki której może usunąć odpowiedni wpis, a także zmodyfikowany obiekt MyObject, co pozwala mu dodać referencje pod nową nazwą. Teraz już można zmieniać nazwy w te i wewtę, coprawda pozostała jeszcze implementacja zachowanai w przypadku znalezienia takiego samego obiektu w mapie, no ale to zostawię już jako zadanie domowe dla czytelnika.