L'implementazione semplificata dei comportamenti ha un unico thread, che realizza i messaggi come invocazione non parallela di metodi dell'oggetto destinazione.
public class Autobus {
…
// metodo di ricezione di un messaggio
public void entraautorimessa(Autista mittente, Autorimessa rimessa) {
if (this.stato == Stato.INSERVIZIO) {
if (mittente.equals(this.precedente))
…
// invio messaggio all'autista this.precedente
this.precedente.entrata(this);
// invio messaggio all'autorimessa
this.autorimessa.entrata(this);
}
}
…
}
Già da questo esempio si vede il problema: viene invocato prima this.precedente.entrata(this); e poi this.autorimessa.entrata(this);, in sequenza; finchè il primo non è terminato, il secondo non parte. Ma il primo metodo può a sua volta inviare altri messaggi, che possono causare l'invio di altri ancora. Fino a che tutto questo non è terminato, this.autorimessa.entrata(this); non inizia nemmeno.
Se per esempio l'autista che è il destinatario del primo messaggio si mette in attesa che l'autorimessa contenga l'autobus fra i suoi, ma l'autorimessa lo inserise solo alla ricezione del suo messaggio, tutto si blocca. L'autobus invia il messaggio all'autista e aspetta che questo abbia terminato per inviarlo anche all'autorimessa. Ma l'autista sta aspettando quello che fa l'autorimessa quando riceve il suo messaggio.
L'implementazione definitiva:
Per ogni oggetto che può ricevere messaggi si crea una coda di messaggi e un thread. Quando viene inviato un messaggio Entrata a uno specifico oggetto Autobus:
I messaggi sono di classi diverse perchè ognuno può avere diversi parametri oltre al mittente. Per esempio, il messaggio di uscita in autorimessa non ha altro che il mittente, ma il messaggio di entrata ha l'autorimessa come parametro. Ogni messaggio va quindi realizzato da una sua classe: Uscita, Entrata, Rottura, Trasporto, Fine, Periodica.
L'oggetto Autobus potrebbe anche avere un metodo di ricezione per ogni tipo di evento, ma questo complicherebbe il codice. Si preferisce invece un unico metodo che riceve tutti i messaggi, di tutti i tipi:
public class Autobus {
…
public void fired(Messaggio e) {
…
}
}
Questo richiede che ogni evento sia una sottoclasse della classe Messaggio:
public abstract class Messaggio {
private Listener mittente;
public Evento(Listener mittente) {
this.mittente = mittente;
}
}
I messaggi sono tutti sottoclassi di Messaggio. Possono o meno avere parametri aggiuntivi, oltre al mittente.
public class Uscita extends Messaggio {
public Uscita(Listener mittente) {
super(mittente);
}
}
public class Entrata extends Messaggio {
private Autorimessa autorimessa;
public Entrata(Listener mittente, Autorimessa destinazione) {
super(mittente);
this.autorimessa = autorimessa;
}
}
Il mittente può essere un qualsiasi oggetto, ma si preferisce definire gli oggetti che hanno un comportamento dinamico come implementazione di una interfaccia.
public interface Listener {
public void fired(Messaggio);
}
public class Autobus implements Listener {
…
public void fired(Messaggio e) {
…
}
}
Dal momento che i metodi fired possono accedere alle classi dati, vanno sincronizzati fra loro e con tutte le attività elementari. La loro esecuzione avviene attraverso l'oggetto unico, come per le attività elementari:
public class Autobus implements Listener {
…
public void fired(Messaggio e) {
Unico.creaUnico().esegui(…);
}
}
L'argomento di esegui in Unico è un oggetto di tipo Attivita. Si definisce un oggetto di questo tipo anche se non è una attività.
public class Autobus implements Listener {
public void fired(Messaggio e) {
Unico.creaUnico().esegui(new AutobusFired(…));
}
}
public class AutobusFired implements Attivita {
@Override
public void esegui() {
…
}
}
Il nome della classe Attivita è ora inadeguato perché include non solo le attività ma anche i comportamenti. Il nome scelto per includere entrambi è Task.
public interface Task {
public void esegui();
}
public class AutobusFired implements Task {
@Override
public void esegui() {
…
}
}