Observer

Pattern descritto in Design Pattern.

Lo scopo del pattern Observer (aka Publish/Subscribe) é quello di definire una dipendenza uno a molti tra oggetti in modo che quando lo stato dell'uno cambia, tutti i dipendenti ricevono una notifica del fatto.

Partecipanti
  • Subject: il soggetto osservato, sa chi lo osserva, non vi sono limiti teorici sul numero degli osservatori. Fornisce un'interfaccia per connettere e disconnettere gli osservatori.
  • Observer: definisce l'interfaccia dell'osservatore, mettendo a disposizione un metodo per notificare i cambiamenti del Subject.
  • ConcreteSubject: tiene traccia dello stato del soggetto
  • ConcreteObserver: mantiene un riferimento al Subject; implementa l'interfaccia Observer;
Esempio

In Java esiste una implementazione standard di questo pattern, nel package java.util é infatti definita la classe Observable e l'interfaccia Observer.

Notiamo che Observable é una classe, mentre il pattern prevederebbe l'uso di un'interfaccia. Questo, considerando inoltre le limitazioni di Java che non permette l'ereditarietà multipla, porta a dover sottostare a dei compromessi se si usa questa implementazione.

Nel nostro caso non abbiamo problemi particolari e quindi usiamo volentieri il codice che ci troviamo a disposizione.

Consideriamo nella nostra applicazione fantasy il caso di un personaggio che riesca con il suo carisma a raggruppare intorno a sé un manipolo di seguaci. Il seguace guadagna da questa relazione un bonus che lo renderà più forte in certe situazioni, ma paga questo con un certo assoggettamento ai voleri del master che, notificandolo ai seguaci, ottiene che questi lo combattano al posto suo.

Questo vale solo se il carisma del master é superiore al desiderio di indipendenza del seguace. Altrimenti il seguace non ci pensa due volte a lasciare il master per conto suo.

Master

Il master della situazione sarà un oggetto di tipo derivato da java.util.Observable, avrà un nome, e un carisma (rappresentato da un intero). Il nome del master non può cambiare nel corso della sua vita, percui avremo un getter ma non un setter per questa proprietà.

Il nostro master può fare fondamentalmente tre cose che hanno un influsso sui suoi seguaci:
cambiare il livello del proprio carisma: il seguace dovrà decidere se essere ancora legato al master in seguito a questo cambiamento;
cercare di aggiungere un seguace: se il seguace é d'accordo, gioirà della relazione, altrimenti annullerà immediatamente l'iscrizione;
attaccare un nemico: l'istanza sgradita al master viene passata ai seguaci, che lo attaccheranno al posto suo.

Vediamo dunque il codice per la classe Master risultante:

package gof.observer;

import java.util.Observable;
import java.util.Observer;

public class Master extends Observable {
private final String name;
private int charisma;

public Master(String name, int charisma) {
this.name = name;
this.charisma = charisma;
}

public String getName() {
return name;
}

public int getCharisma() {
return charisma;
}

public void setCharisma(int charisma) {
this.charisma = charisma;

System.out.println(name + ": \"My charisma has changed.\"");
setChanged();
notifyObservers();
System.out.println("---");
}

@Override
public synchronized void addObserver(Observer o) {
super.addObserver(o);
setChanged();
notifyObservers();
System.out.println("---");
}

public void attack(Warrior enemy) {
System.out.println(name + ": \"I have an enemy!\"");
setChanged();
notifyObservers(enemy);
System.out.println("---");
}
}

Warrior

In questa prima versione il master può raccogliere seguaci solo tra i Guerrieri, quindi abbiamo una classe Warrior che implementa l'interfaccia java.util.Observer, dichiarando inoltre un riferimento al master corrente per il guerriero (inizialmente sarà null, e il guerriero sarà libero), il suo livello di indipendenza (un intero) e il bonus che ottiene in cambio del suo legame al master (zero in caso di indipendenza)

Le tre attività peculiari del guerriero in quanto dipendente a un master sono quelle di elevare una lode al master, abbandonarlo (il che comporta annullare il proprio bonus), e combattere un guerriero segnalatogli dal master stesso. Sono tutti metodi privati, dato che verranno richiamati internamente alla classe stessa.

Il metodo chiave del pattern é update(), dichiarato nell'interfaccia Observer, e che implementiamo qui in questo modo: ci accertiamo che la richiesta stia arrivando da un Master, altrimenti la rifiutiamo (sdegnosamente, un guerriero accetta solo Master come master); se il guerriero non ha già un master, lo accetta, altrimenti declina l'offerta (un guerriero é fedele al proprio master, finchè ha un adeguato carisma); controlliamo il carisma del nostro nuovo master, se inferiore alle aspettative, lo abbandoniamo; aggiorniamo il nostro bonus, in funzione del carisma del master (un quarto del suo carisma, in questa versione); se il master non ci ha passato lavoro sporco da fare, eleviamo la nostra lode a lui; altrimenti controlliamo cosa ci ha passato, se si tratta di un guerriero lo combattiamo.

Ecco il codice per questa classe:

package gof.observer;

import java.util.Observable;
import java.util.Observer;

public class Warrior implements Observer {
Master master;
int indipendence;
int bonus = 0;

private String name;

public String getName() {
return name;
}

public Warrior(String name, int indipendence) {
this.indipendence = indipendence;
this.name = name;
}

public void update(Observable o, Object arg) {
if(!(o instanceof Master)) {
return;
}
if(master == null)
master = (Master)o;
else if(o != master) {
return;
}

if(master.getCharisma() < indipendence) {
master.deleteObserver(this);
leave();
return;
}

bonus = master.getCharisma() / 4;
if(arg == null) {
prise();
return;
}

if(arg instanceof Warrior) {
fight((Warrior)arg);
}
}

private void prise() {
System.out.println(name + ": \"" + master.getName() + " is cool.\"");
}

private void leave() {
System.out.println(name + ": \"I don't like " + master.getName() + ".\"");
master = null;
bonus = 0;
}

private void fight(Warrior w) {
System.out.println(name + ": \"" + w.getName() + " is my enemy!\"");
}
}

Main

Ci resta da scrivere un piccolo test client in cui creiamo un master, un paio di guerrieri, li associamo al master, creiamo un terzo guerriero che viene attaccato dal master, in seguito a questa disdicevole azione il master perde buona parte del suo carisma, il che lo porterà a perdere un membro del suo seguito, nonostante ciò il master itera il suo attacco contro il guerriero:

public static void main(String[] args) {
Master master = new Master("MasterPlaster", 12);

Warrior tom = new Warrior("Tom", 6);
Warrior bob = new Warrior("Bob", 8);

master.addObserver(bob);
master.addObserver(tom);

Warrior bill = new Warrior("Bill", 12);
master.attack(bill);

master.setCharisma(7);
master.attack(bill);
}

Questo l'output del nostro test client:

Bob: "MasterPlaster is cool."
---
Tom: "MasterPlaster is cool."
Bob: "MasterPlaster is cool."
---
MasterPlaster: "I have an enemy!"
Tom: "Bill is my enemy!"
Bob: "Bill is my enemy!"
---
MasterPlaster: "My charisma has changed."
Tom: "MasterPlaster is cool."
Bob: "I don't like MasterPlaster."
---
MasterPlaster: "I have an enemy!"
Tom: "Bill is my enemy!"

Nessun commento:

Posta un commento