Una sottoscrizione durable

Altro esempio tratto dalla sezione "Robust JMS Applications" del tutorial Sun su Java EE, parte dedicata a JMS é il Durable Subscriber che trattiamo in questa serie di post.

Il punto di questo esempio é che vogliamo che il subscriber possa disconnettersi da JMS senza perdere messaggi a lui indirizzati. Usiamo perciò una connessione durable, in modo da poter creare un TopicSubscriber durable.

La gestione di basso livello per JMS, con la creazione del contesto iniziale, delle connection factory e del topic é la medesima che abbiamo già visto in precedenza, inglobata nella classe JmsConnectionManager, perciò non ne ripetiamo qui i dettagli.

MultiplePublisher

Il publisher é piuttosto semplice, usa la connessione non durable, su cui crea una sessione AUTO_ACKNOWLEDGE, il producer é un MessageProducer costruito sulla session specificando il topic che abbiamo definito precedentemente via Admin Console di GlassFish.

In pratica ci si limita a mandare un certo numero di messaggi sul topic ogni volta che viene invocato il metodo publishMessages(); ecco qui a seguire il codice della classe:

package durable;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;

public class MultiplePublisher {
private Connection connection = null;
private Session session = null;
private MessageProducer producer;
private JmsConnectionManager cm = JmsConnectionManager.getInstance();

private int msgBaseIndex = 0;

public MultiplePublisher() throws JMSException {
connection = cm.connFact.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

producer = session.createProducer(cm.topic);
}

public void publishMessages() {
final int NUMMSGS = 3;
final String MSG_TEXT = "This is message ";

try {
TextMessage message = session.createTextMessage();

for(int i = 1; i <= NUMMSGS; ++i) {
message.setText(MSG_TEXT + (msgBaseIndex + i));
System.out.println("Publishing message: " + message.getText());
producer.send(message);
}
msgBaseIndex += NUMMSGS;

// non-text message as end of messages
producer.send(session.createMessage());
}
catch(JMSException e) {
System.err.println("Exception in publishMessages: " + e.toString());
}
}

public void finish() {
if(connection != null) {
try {
connection.close();
}
catch(JMSException e) {}
}
}
}

DurableSubscriber

Il subscriber di questo esempio é simile al subscriber dell'esempio precedente, e ricicliamo per il suo uso le classi TextListener e Monitor, che mettono a disposizione rispettivamente il metodo onMessage() che gestisce i messaggi come indirizzati dal topic e il meccanismo per sincronizzare DurableSubscriber con il listener.

Il suo costruttore crea una connessione durable, e da questa una sessione di tipo AUTO_ACKNOWLEDGE.

La ricezione di messaggi é gestita da due metodi, startSubscriber() e closeSubscriber().

La startSubscriber() crea un durable subscriber per il topic usato dal publisher, crea un listener, di tipo TextListener - che abbiamo già trattato nel post precedente, e lo associa al subscriber. Quindi facciamo partire la connessione.

La closeSubscriber() aspetta che il listener abbia completato la ricezione dei messaggi, quindi chiude il subscriber e ferma la connessione.

Vediamo il codice della classe:

package durable;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.jms.TopicSubscriber;

public class DurableSubscriber {
private Connection connection = null;
private Session session = null;
private TextListener listener = null;
private TopicSubscriber subscriber = null;

private JmsConnectionManager cm = JmsConnectionManager.getInstance();

public DurableSubscriber() throws JMSException {
connection = cm.drbConnFact.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
}

public void startSubscriber() throws JMSException {
System.out.println("Starting subscriber");

subscriber = session.createDurableSubscriber(cm.topic, "MakeItLast");
listener = new TextListener();
subscriber.setMessageListener(listener);
connection.start();
}

public void closeSubscriber() throws JMSException {
listener.waitTillDone();

System.out.println("Closing subscriber");
subscriber.close();
connection.stop();
}

public void finish() {
if (connection != null) {
System.out.println("Unsubscribing from durable subscription");
try {
session.unsubscribe("MakeItLast");
connection.close();
}
catch(JMSException je) {
System.out.println(je.getMessage());
}
}
}
}

Main

Il main fa partire e poi chiude due volte il subscriber. Se non fosse durable i messaggi mandati dal publisher quando il subscriber é giù andrebbero persi. Eseguendo il codice vediamo che, come ci aspettiamo, i messaggi restano sul topic in attesa che il subscriber venga fatto ripartire.

public static void main(String[] args) {
DurableSubscriber subscriber = null;
MultiplePublisher publisher = null;

try {
subscriber = new DurableSubscriber();
publisher = new MultiplePublisher();

subscriber.startSubscriber();
publisher.publishMessages();
subscriber.closeSubscriber();

publisher.publishMessages();

subscriber.startSubscriber();
subscriber.closeSubscriber();
}
catch(JMSException je) {
System.out.println("Exception: " + je.getMessage());
}
finally {
if(subscriber != null)
subscriber.finish();
if(publisher != null)
publisher.finish();
}

System.exit(0);
}

Javafx plugin per Netbeans 6.7

Finalmente disponibile il pluging per NetBeans 6.7!

Sincronizzarsi su di una coda: Subscriber/2

Ultimo passo nel completare l'esempio che abbiamo cominciato a vedere un paio di post fa, sullo scambio di messaggi tra un publisher e un subscriber per mezzo di un topic, usando una coda per sincronizzarsi.

Ci resta da vedere come implementare il listener che deve effettivamente leggere e gestire i messaggi che arrivano dal publisher per conto del subscriber.

TextListener

Abbiamo visto che il metodo receive() del nostro subscriver delega a questa classe la lettura dei messaggi, e resta in attesa, chiamando il suo metodo waitTillDone(), che l'operazione venga completata. Vediamo nel dettaglio come ciò avviene:

package jmsrobust;

import javax.jms.JMSException;
import javax.jms.MessageListener;
import javax.jms.Message;
import javax.jms.TextMessage;

public class TextListener implements MessageListener {
private Monitor monitor = new Monitor();

public void waitTillDone() {
monitor.waitTillDone();
}

public void onMessage(Message message) {
if(message instanceof TextMessage) {
TextMessage msg = (TextMessage)message;

try {
System.out.println("Processing message: " + msg.getText());
}
catch (JMSException je) {
System.err.println("Exception in onMessage(): " + je.toString());
}
}
else {
// non-text message == end of the message stream
monitor.allDone();
}
}
}

Sembra tutto molto semplice, e in effetti lo é. Il metodo onMessage(), dichiarato nell'interfaccia MessageListener che la nostra classe implementa, processa i messaggi che ci vengono passati da JMS - ricordiamo che questo listener viene associato dal metodo receive() del nostro subscriber al topic usato come canale si comunicazione - finché non si vede un messaggio non testuale che abbiamo stabilito essere la convenzione che ci indica il termine delle trasmissioni. Segnaliamo questo al monitor che usiamo allo scopo di sincronizzare il TextListener con chi lo possiede.

Vediamoci dunque come implementiamo il monitor:

package jmsrobust;

public class Monitor {
private boolean done = false;

public synchronized void waitTillDone() {
while(!done) {
System.out.println("Waiting for notification");
try {
this.wait();
}
catch (InterruptedException ie) {
System.out.println("Wait interrupted");
}
}
System.out.println("Notification received");
}

public synchronized void allDone() {
done = true;
System.out.println("allDone notification");
this.notify();
}
}

In realtà mi sembra un po' sovrabbondante definire i metodi come sincronizzati, dato che non vengono chiamati concorrentemente, ma potrebbe essere utile in una situazione più complessa. Per il momento manteniamo il codice così, in attesa di valutare meglio i pro e i contro della decisione.

Per il resto, il codice dovrebbe essere abbastanza intuitivo. Con waitTillDone() mettiamo il thread che lo chiama in attesa. E questa attesa viene risolta con una chiamata a allDone() che cambia lo stato della variabile di controllo e notifica all'oggetto stesso che ha cambiato stato.

Test

Per testare la fuzionalità usiamo questo metodo:

private static void pubSub() {
System.out.println("Start multi publisher example");
Thread subscriber = new AsynchSubscriber();
Thread publisher = new MultiplePublisher();

try {
subscriber.start();
publisher.start();

subscriber.join();
publisher.join();
}
catch (InterruptedException e) {
e.printStackTrace(System.out);
}

System.out.println("Multi publisher example completed");
}

Sincronizzarsi su di una coda: Subscriber

Continuiamo l'esercizio tratto dal tutorial Sun su Java EE, sezione "Creating Robust JMS Applications", in cui un publisher su topic si sincronizza con il subscriber per mezzo di una coda dedicata.

Nel post precedente abbiamo visto il publisher, ora vediamo il subscriber.

AsynchSubscriber

Anche questa classe é una estensione di Thread, il cui metodo run() é questo:

@Override
public void run() {
if(startup()) {
System.out.println("Subscriber ready to start");

receive();
}

closeConnections();
System.out.println("Subscriber run completed");
}

Poco da dire su startup, se non che qui usiamo anche una Durable Connection - ci serve per poter instanziare un durable subscriber - mentre le sessioni sono entrambe di tipo AUTO_ACKNOWLEDGE.

Molto da dire invece sulla receive(). Semplificando, l'idea é quella di instanziare un TopicSubscriber sul topic definito nel nostro Application Server, metterci sopra un listener che si occuperà di trattare i messaggi ricevuti e far partire la connessione.

Comunichiamo quindi al publisher che siamo pronti, aspettiamo che il listener ci confermi che la ricezione é completata e quindi procediamo al cleanup delle risorse utilizzate.

Vediamo il codice:

private void receive() {
JmsConnectionManager cm = JmsConnectionManager.getInstance();

try {
TopicSubscriber subscriber =
sessions[0].createDurableSubscriber(cm.topic, "AckSub");

TextListener listener = new TextListener();
subscriber.setMessageListener(listener);
conns[0].start();

if(synchronize() == false) {
System.out.println("Can't synchronize");
return;
}

listener.waitTillDone();
subscriber.close();
sessions[0].unsubscribe("AckSub");
} catch (JMSException e) {
System.err.println("Exception occurred: " + e.toString());
}
}

La synchronize() é una funzione piuttosto semplice, si limita a mandare un messaggio alla coda che utilizziamo per sincronizzare subscriber e producer.

Vediamo a seguire il codice di tutta la classe:

package jmsrobust;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.TopicSubscriber;

public class AsynchSubscriber extends Thread {
enum Type {DURABLE, STANDARD};

private Connection conns[] = new Connection[2];
private Session sessions[] = new Session[2];

private boolean startup() {
JmsConnectionManager cm = JmsConnectionManager.getInstance();

try {
int i = Type.DURABLE.ordinal();
conns[i] = cm.drbConnFact.createConnection();
sessions[i] = conns[i].createSession(false, Session.AUTO_ACKNOWLEDGE);

i = Type.STANDARD.ordinal();
conns[i] = cm.connFact.createConnection();
sessions[i] = conns[i].createSession(false, Session.AUTO_ACKNOWLEDGE);

return true;
}
catch (Exception e) {
System.err.println("Connection problem: " + e.toString());
}

return false;
}

private void closeConnections() {
for(Type type : Type.values()) {
int i = type.ordinal();
try {
if(conns[i] != null) {
conns[i].close();
conns[i] = null;
}
}
catch(JMSException ee) {}
}
}

private void receive() {
JmsConnectionManager cm = JmsConnectionManager.getInstance();

try {
// start a listener on a subscriber
TopicSubscriber subscriber =
sessions[0].createDurableSubscriber(cm.topic, "AckSub");

TextListener listener = new TextListener();
subscriber.setMessageListener(listener);
conns[0].start();

// Let the publisher know that subscriber is ready.
if(synchronize() == false) {
System.out.println("Can't synchronize");
return;
}

// wait for notification from publisher
listener.waitTillDone();
subscriber.close();
sessions[0].unsubscribe("AckSub");
} catch (JMSException e) {
System.err.println("Exception occurred: " + e.toString());
}
}

/**
* Sends a message to the queue to notify a publisher that
* it is ready to receive messages.
*/
private boolean synchronize() {
JmsConnectionManager cm = JmsConnectionManager.getInstance();

try {
MessageProducer producer = sessions[1].createProducer(cm.queue);
TextMessage message = sessions[1].createTextMessage();
message.setText("synchronize");
System.out.println("Subscriber is sending synchronize message");
producer.send(message);
} catch (JMSException e) {
System.err.println("Exception occurred for the subscriber: " + e.toString());
return false;
}

return true;
}

/**
* Runs the thread.
*/
@Override
public void run() {
if(startup()) {
System.out.println("Subscriber ready to start");

receive();
}

closeConnections();
System.out.println("Subscriber run completed");
}
}

Ci resta da vedere il codice della TextListener (con la associata classe Monitor) e il main che ci permette di testare le funzionalità, vediamole nel post successivo.

Sincronizzarsi su di una coda: Publisher

Il secondo esempio presentato nel tutorial Java EE di Sun nel paragrafo "Creating Robust JMS Applications", presenta uno scambio di messaggi tra publisher e subcriber che usano una coda accessoria per sincronizzarsi.

Ridisegno il codice presentato, perché mi é sembrato un po' caotico, sperando di ottenere qualcosa di più leggibile.

MultiplePublisher

La classe MultiplePublisher si prende il compito di postare i messaggi su di un Topic per l'uso del subscriber che vedremo più avanti.

Avendo deciso di lavorare con un topic, invece con una queue, dobbiamo aspettare che il ricevente ci dica quando é pronto a ricevere, per evitare che i nostri messaggi vadano persi.

Il metodo run() della classe, che estende Thread, sarà questo:

@Override
public void run() {
if(startup()) {
System.out.println("Publisher ready to start");

if(synchronize())
sendMessages();
}

closeConnections();
System.out.println("Publisher run completed");
}

Dopo la fase di inizializzazione, definita in startup(), ci si sincronizza con il destinatario, e solo se la sincronizzazione ha successo si mandano i messaggi.

La sincronizzazione avviene appoggiandosi su una coda e mettendosi in attesa che arrivi su di essa un messaggio prestabilito:

public boolean synchronize() {
JmsConnectionManager cm = JmsConnectionManager.getInstance();

try {
System.out.println("Publisher is synchronizing on the control queue");
MessageConsumer receiver = sessions[1].createConsumer(cm.queue);
conns[1].start();

Message message = receiver.receive(2000);
if(message instanceof TextMessage) {
TextMessage text = (TextMessage) message;
if(text.getText().equals("synchronize")) {
System.out.println("Publisher has received synchronize message");
return true;
}
}
System.out.println("Publisher has not received synchronize message");
} catch (JMSException e) {
System.err.println("Exception occurred: " + e.toString());
}

return false;
}

In questo caso abbiamo deciso di aspettare per due secondi, se nulla arriva nel termine stabilito, diamo per fallita la connessione. Un esempio più realistico sarebbe più opportuno prevedere un meccanismo più sofisticato.

Vediamo ora il codice completo per questa classe. Notiamo che le due sessioni utilizzate (una per il topic su cui avviene il reale scambio di messaggi, una per la coda di controllo) sono create in modalità AUTO_ACKNOWLEDGE.

package jmsrobust;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;

public class MultiplePublisher extends Thread {
private final int NUMMSGS = 3;
private final String MSG_TEXT = "Here is an auto-acknowledge message";

private Connection conns[] = new Connection[2];
private Session sessions[] = new Session[2];

private boolean startup() {
JmsConnectionManager cm = JmsConnectionManager.getInstance();

try {
for(int i = 0; i < 2; ++i) {
conns[i] = cm.connFact.createConnection();
sessions[i] = conns[i].createSession(false, Session.AUTO_ACKNOWLEDGE);
}
return true;
}
catch (Exception e) {
System.err.println("Connection problem: " + e.toString());
}

return false;
}

private void closeConnections() {
for(int i = 0; i < 2; ++i) {
try {
if(conns[i] != null) {
conns[i].close();
conns[i] = null;
}
}
catch(JMSException ee) {}
}
}

private void sendMessages() {
JmsConnectionManager cm = JmsConnectionManager.getInstance();

MessageProducer publisher = null;
TextMessage message = null;
try {
publisher = sessions[0].createProducer(cm.topic);
message = sessions[0].createTextMessage();

for (int i = 0; i < NUMMSGS; i++) {
message.setText(MSG_TEXT + " " + i);
System.out.println("Publishing message: " + message.getText());
publisher.send(message);
}

System.out.println("Sending end of messages");
publisher.send(sessions[0].createMessage());
} catch (JMSException e) {
System.err.println("Exception occurred: " + e.toString());
}
}

@Override
public void run() {
if(startup()) {
System.out.println("Publisher ready to start");

if(synchronize())
sendMessages();
}

closeConnections();
System.out.println("Publisher run completed");
}

public boolean synchronize() {
JmsConnectionManager cm = JmsConnectionManager.getInstance();

try {
System.out.println("Publisher is synchronizing on the control queue");
MessageConsumer receiver = sessions[1].createConsumer(cm.queue);
conns[1].start();

Message message = receiver.receive(2000);
if(message instanceof TextMessage) {
TextMessage text = (TextMessage) message;
if(text.getText().equals("synchronize")) {
System.out.println("Publisher has received synchronize message");
return true;
}
}
System.out.println("Publisher has not received synchronize message");
} catch (JMSException e) {
System.err.println("Exception occurred: " + e.toString());
}

return false;
}
}

I dettagli implementativi della connessione a JMS sono nella classe JmsConnectionManager, che abbiamo visto nel precedente post dedicato all'argomento.

Iterator

Pattern descritto in Design Pattern.

Lo scopo del pattern Iterator é quello di fornire una di accesso sequenziale agli elementi di un oggetto aggregato senza esporre la rappresentazione interna.

Esempio

L'interfaccia Iterator disponibile in Java, java.util.Iterator, é leggermente diversa da quella proposta dalla Gang of Four, in particolare prevede anche un metodo per rimuovere l'elemento corrente dalla collezione sottostante. Usiamo comunque quella nel nostro esempio.

Il problema del nostro mondo fantasy che utilizziamo per il nostro esempio é quello di implementare una classe che descrive un tipo peculiare di personaggi che posseggono mezza dozzina di mostri al loro servizio. Questa implementazione prevede che i mostri siano esattamente sei, vengono creati assieme al personaggio ed esistono solo in quanto appartenenti al personaggio stesso.

Per questo motivo li implementiamo con un array e li rendiamo disponibili all'utente della classe per mezzo di un iteratore.

Questo é il codice relativo alla nostra classe Monster, in una implementazione minimale:

package gof.iterator;

public class Monster {
private final int number;
private boolean active = true;

public Monster(int number) {
this.number = number;
}

public int getNumber() {
return number;
}

public boolean isActive() {
return active;
}

public void setActive(boolean active) {
this.active = active;
}
}

La classe che rappresenta il padrone dei mostri viene in questo modo modo:

package gof.iterator;

import java.util.Iterator;

public class MonsterMaster {
private final String name;
private Monster[] monsters = new Monster[6];

public MonsterMaster(String name) {
this.name = name;

for(int i = 0; i < 6; ++i)
monsters[i] = new Monster(i + 1);
}

public String getName() {
return name;
}

public Iterator getIterator() {
return new MonsterIterator(monsters);
}
}

E questa é l'implementazione per il nostro iteratore, che usa come collezione sottostante un array di mostri, come richiesto:

package gof.iterator;

import java.util.Iterator;

public class MonsterIterator implements Iterator {
private Monster[] monsters;
private int current = 0;

public MonsterIterator(Monster[] monsters) {
this.monsters = monsters;
}

public boolean hasNext() {
if(current == monsters.length)
return false;
return true;
}

public Object next() {
if(current == monsters.length)
return null;

return monsters[current++];
}

public void remove() {
monsters[current].setActive(false);
}
}

Verifichiamo la nostra implementazione con questa funzione:

public static void main(String[] args) {
MonsterMaster master = new MonsterMaster("MoMa");

Iterator it = master.getIterator();

System.out.println("List of monsters for " + master.getName());
while(it.hasNext()) {
Monster m = it.next();

System.out.print(m.getNumber() + " ");
}
System.out.println();
}

Messaggi con ricevuta

Per il momento trattiamo sessioni senza transazioni, in questo caso la gestione delle ricevute é determinata da come la sessione viene creata, il che può avvenire in una di queste tre modalità:
  • AUTO_ACKNOWLEDGE, la ricevuta é gestita automaticamente dalla sessione;
  • CLIENT_ACKNOWLEDGE, il client chiama esplicitamente il metodo acknowledge() del messaggio ricevuto per notificare la ricezione. Nota che la notifica non vale per il solo messaggio su cui si chiama il metodo, ma per tutti i messaggi consumati dal client;
  • DUPS_OK_ACKNOWLEDGE, gestione automatica minimizzando i controlli della sessione per evitare duplicazioni di messaggi, permette una gestione più leggera a costo del rischio di duplicazione di messaggi (il campo JMSRedelivered viene messo a true per segnalare messaggi duplicati).
I messaggi senza acknowledgment prima del termine della sessione vengono tenuti in sospeso e rimandati alla nuova connessione alla coda, e anche al topic, nel caso sia associato a un durable TopicSubscriber.

Lavoriamo sul codice fornito dal tutorial Sun su Java EE nel folder jms/advanced/ackequivexample.

L'esempio che sviluppiamo qui mostra come nel caso di un receiver sincrono la ricevuta di ritorno per un messaggio venga generata al termine della gestione.

Specificando CLIENT_ACKNOWLEDGE come modalità di sessione, daremo la ricevuta di ritorno esplicitamente al termine della gestione.

Applicazione Java

Creamo una nuova applicazione Java SE, che chiamiamo jmsRobust e, come negli esempi precedenti, aggiungiamo i jar che ci permettono di interagire con l'application server:
  • appserv-admin.jar
  • appserv-launch.jar
  • appserv-rt.jar
  • imq.jar
  • imqbroker.jar
  • imqjmsra.jar
  • javaee.jar
  • jms.jar
JmsConnectionManager

Creiamo un unico punto di accesso a JMS, la classe JmsConnectionManager, un singleton che va progettato con un minimo di attenzione, visto l'ambiente multithreading in cui ci troviamo.

Al solito, l'URL utilizzata é quella del localhost, nel caso l'application server sia su un'altra macchina occorre fornire l'indirizzo corretto.

Per il resto il codice dovrebbe essere abbastanza leggibile:

package jmsrobust;

import java.util.Hashtable;
import javax.jms.ConnectionFactory;
import javax.jms.Queue;
import javax.jms.Topic;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class JmsConnectionManager {
private static final String AS_URL = "iiop://127.0.0.1:3700";
private static final String AS_CLASS = "com.sun.appserv.naming.S1ASCtxFactory";
private static final String CONNECTION = "jms/tConnectionFactory";
private static final String QUEUE = "jms/tQueue";
private static final String TOPIC = "jms/tTopic";

private static volatile JmsConnectionManager instance;

protected ConnectionFactory connFact;
protected Queue queue;
protected Queue ctrlQueue;
protected Topic topic;

private Context ctx;

private JmsConnectionManager() {
System.out.println(Thread.currentThread().getName() + ": initializing the context");

Hashtable properties = new Hashtable(2);
properties.put(Context.PROVIDER_URL, AS_URL);
properties.put(Context.INITIAL_CONTEXT_FACTORY, AS_CLASS);
try {
ctx = new InitialContext(properties);
System.out.println(Thread.currentThread().getName() + ": context initialized");

System.out.println(Thread.currentThread().getName() + ": lookup step");
connFact = (ConnectionFactory)ctx.lookup(CONNECTION);

queue = (Queue)ctx.lookup(QUEUE);
topic = (Topic)ctx.lookup(TOPIC);
System.out.println(Thread.currentThread().getName() + ": lookup done");
}
catch(NamingException ne) {
ne.printStackTrace();
}
}

public static JmsConnectionManager getInstance() {
if(instance == null) {
System.out.println(Thread.currentThread().getName() + ": init required");
synchronized(JmsConnectionManager.class) {
if(instance == null) {
System.out.println(Thread.currentThread().getName() + ": init really required");
instance = new JmsConnectionManager();
System.out.println(Thread.currentThread().getName() + ": init done");
}
}
}

return instance;
}
}

SynchClient

Dobbiamo ora creare due classi, un sender e un receiver che usino la coda per scambiarsi messaggi. Notiamo però che le due classi sarebbero molto simili, c'é una prima fase di startup, uguale per entrambi, in cui si crea una connessione e con questa una sessione; segue un passo specifico in cui il sender manda i messaggi e il receiver li riceve, e infine c'é il cleanup in cui si chiude la connessione.

Tutto ciò ci spinge ad usare il pattern Template Method. La SynchClient sarà la classe astratta alla base della nostra gerarchia:

package jmsrobust;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Session;

public abstract class SynchClient extends Thread {
protected Connection connection;
protected Session session;

private boolean startup() {
JmsConnectionManager cm = JmsConnectionManager.getInstance();

try {
connection = cm.connFact.createConnection();
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
return true;
}
catch (Exception e) {
System.err.println("Connection problem: " + e.toString());
}

return false;
}

private void closeConnection() {
if (connection != null) {
try { connection.close();}
catch (JMSException ee) {}
}
}

protected abstract void operation();

@Override
public void run() {
if(startup())
operation();
closeConnection();
}
}

Il template method del pattern é qui run(), che definisce come il codice viene eseguito dalle classi derivate. Due dei tre passi sono definiti in questa classe, startup() e closeConnection(), c'é poi il metodo dichiarato qui come abstract, che andrà definito nelle classi derivate.

La cosa importante da notare dal punto di vista di JMS é che la sessione viene creata in modalità CLIENT_ACKNOWLEDGE, dunque toccherà a chi riceve dare conferma esplicita del messaggio ricevuto.

SynchSender

Il sender implementa operation() con lo scopo di mettere un messaggio sulla coda. Usa la session creata da startup() per creare un MessageProducer che faccia riferimento alla coda che abbiamo istanziato in JmsConnectionManager:

package jmsrobust;

import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.TextMessage;

public class SynchSender extends SynchClient {
final String MSG_TEXT = "Please explicitly acknowledge this";

protected void operation() {
JmsConnectionManager cm = JmsConnectionManager.getInstance();

try {
MessageProducer producer = session.createProducer(cm.queue);
TextMessage message = session.createTextMessage();

long secs = System.currentTimeMillis() % 10000;
message.setText(MSG_TEXT + " (" + secs + ")");

System.out.println("Sending message: " + message.getText());
producer.send(message);
} catch (JMSException e) {
System.err.println("Exception occurred: " + e.toString());
}
}
}

SynchReceiver

Il receiver implementa operation() per leggere un messaggio dalla coda, crea quindi un MessageConsumer sulla coda usata anche dal sender, fa partire la connessione e si mette in attesa di ricevere il messaggio.

Ricevuto il messaggio e compiuta la sua elaborazione viene mandata ricevuta alla coda:

package jmsrobust;

import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.TextMessage;

public class SynchReceiver extends SynchClient {

protected void operation() {
JmsConnectionManager cm = JmsConnectionManager.getInstance();

try {
MessageConsumer receiver = session.createConsumer(cm.queue);
connection.start();
TextMessage message = (TextMessage)receiver.receive();

System.out.println("The receiver processes: " + message.getText());
System.out.println("The receiver acknowledge the message");
message.acknowledge();
} catch (JMSException e) {
System.err.println("Exception occurred: " + e.toString());
}
}
}

Main

Per testare il codice scriviamo questo codice:

private static void clientAck() {
System.out.println("Start explicit client acknowledgment example");
Thread sender = new SynchSender();
Thread receiver = new SynchReceiver();

sender.start();
receiver.start();

try {
sender.join();
receiver.join();
}
catch (InterruptedException e) {
e.printStackTrace(System.out);
}

System.out.println("Explicit client acknowledgment example completed");
}

public static void main(String[] args) {
clientAck();

System.exit(0);
}

Cercare tra i messaggi in coda

Questa volta creiamo un client che ci permette di guardare i messaggi in attesa su una coda senza consumarli.

In pratica il client é molto simile a quelli che consumano i messaggi e abbiamo già visto, con la differenza che il browsing é applicabile solo a code, e che usiamo un oggetto QueueBrowser per accedere la nostra coda.

Il client consiste in due file: Browser.java e Main.java.

Browser

I cambiamenti sono principalmente raccolti nella funzione getMessages(), dove si crea un oggetto QueueBrowser a partire dalla sessione corrente per l'oggetto Queue specificato.

Interessante é il modo di accedere ai messaggi, per cui la classe QueueBrowser mette a disposizione il metodo getEnumeration(), che li ritorna, per l'appunto, come una enumeration.

package browser;

import java.util.Enumeration;
import java.util.Hashtable;
import javax.jms.ConnectionFactory;
import javax.jms.Queue;
import javax.jms.Connection;
import javax.jms.Session;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.QueueBrowser;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class Browser {
private static final String AS_URL = "iiop://127.0.0.1:3700";
private static final String AS_CLASS = "com.sun.appserv.naming.S1ASCtxFactory";
private static final String CONNECTION = "jms/tConnectionFactory";
private static final String QUEUE = "jms/tQueue";

private static Queue queue;

private Connection connection;
private Session session;

private static Context ctx;
static {
System.out.println("initializing the context");

Hashtable properties = new Hashtable(2);
properties.put(Context.PROVIDER_URL, AS_URL);
properties.put(Context.INITIAL_CONTEXT_FACTORY, AS_CLASS);
try {
ctx = new InitialContext(properties);
}
catch (NamingException ex) {
ex.printStackTrace();
}
}


public Browser() throws JMSException, NamingException {

ConnectionFactory f = (ConnectionFactory)ctx.lookup(CONNECTION);
connection = f.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
}

public void getMessages() throws JMSException, NamingException {
// ensure the queue is available
if(queue == null) {
System.out.println("Looking for JMS queue");
queue = (Queue)ctx.lookup(QUEUE);
}

QueueBrowser browser = session.createBrowser(queue);
Enumeration<Message> msgs = browser.getEnumeration();

if(!msgs.hasMoreElements()) {
System.out.println("No messages in queue");
}
else {
while(msgs.hasMoreElements()) {
Message tempMsg = msgs.nextElement();
System.out.println("Message: " + tempMsg);
System.out.println("-------");
}
}
}

public void close() throws JMSException {
System.out.println("Closing JMS connection");
if(connection != null)
connection.close();
}
}

Main

Il main crea semplicemente un oggetto di tipo Browser, chiama la getMessage(), chiude la connessione e termina l'esecuzione.

public static void main(String[] args) {
try {
Browser browser = new Browser();
browser.getMessages();
browser.close();
}
catch(Exception ex) {
System.err.println("Consuming failed: " + ex.toString());
ex.printStackTrace();
System.exit(1);
}
System.exit(0);
}

Singleton

Pattern descritto in Design Pattern.

Lo scopo del pattern Singleton é quello di assicurare che una classe ha una sola istanza, e gli fornisce un punto di accesso globale.

Esempio

E' un pattern molto usato, in quanto accade molto spesso di dover garantire che esista un solo oggetto di una determinata classe. Un esempio per il nostro mondo fantasy potrebbe essere il genio della lampada, alla Alì Babà. Viene descritto da una classe specifica, dato che ha caratteristiche sue peculiari, e ne esiste uno solo.

E' anche un pattern a cui bisogna dedicare una certa attenzione perché tocca un punto delicato. O meglio, se il nostro singleton si trova ad essere eseguito in un singolo thread le cose sono piuttosto semplici, ma sorgono problemi se lavoriamo, come di solito accade in un ambiente "moderno", in multithreading.

Iniziamo a vedere il caso in cui ci sia un solo thread di esecuzione.

Singleton senza multithreading

Dobbiamo regolamentare la creazione del genio, per questo motivo dichiariamo il costruttore standard privato, impedendo di fatto la creazione di genii all'esterno della classe stessa.

Mettiamo quindi a disposizione un metodo statico, per consuetudine chiamato getInstance(), che crea l'unica instanza della classe, se non é ancora stata creata, e la ritorna al chiamante.

Oltre al meccanismo proprio per la gestione del singleton aggiungiamo alla classe una proprietà per tener traccia del fortunato che viene riconosciuto dal genio come suo capo, e metodi per il get e set del master.

La classe andrebbe estesa per gestire tre desideri e quindi resettare il master a null, ma tralasciamo, per il momento.

Il codice minimale é perciò questo:

package gof.singleton;

public class LampGenie {
private static LampGenie instance = null;

public static LampGenie getInstance() {
if(instance == null)
instance = new LampGenie();
return instance;
}

private Human master = null;

private LampGenie() {}

public Human getMaster() {
return master;
}

boolean setMaster(Human newMaster) {
if(master == null) {
System.out.println("Setting the master to " + newMaster.getName());
master = newMaster;
return true;
}
else {
System.out.println("I already have a master: " + master.getName());
return false;
}
}
}

Singleton in multithreading

Ma che succede alla getInstance() se il codice é eseguito contemporaneamente da più thread? In linea teorica possiamo avere grossi problemi e, come ci insegnano le Leggi di Murphy, possiamo star certi che nel momento meno opportuno qualcosa di sgradevole capiterà anche nella pratica.

Infatti se un thread trova che instance é correntemente pari a null, potrebbe ben essere, che un altro thread abbia appena fatto lo stesso controllo e gli cambi il valore di instance praticamente sotto i piedi, ottenendo così il risultato inaspettato che entrambi i thread finiscono per creare una nuova instance.

Abbiamo infatti due competitori per un unica risorsa il cui accesso, per non evitare questi pasticci, va regolamentato.

Ci sono diversi modi di superare il problema, in funzione del contesto in cui ci troviamo.

In questo caso abbiamo una risorsa, il genio della lampada, che costa poco creare, ed é normalmente usata nel corso della nostra applicazione (che storia sarebbe mai senza genio della lampada?), percui ci viene naturale prendere la decisione di lasciare che ci pensi la Java Virtual Machine a garantire che le cose vadano per il verso giusto. Chiamiamo il costruttore per il singleton al momendo della sua dichiarazione come oggetto statico. Ci penserà la JVM a chiamare il costruttore garantendo l'assenza di conflitti tra thread.

Se ci va bene questo approccio, faremo questi cambiamenti nella classe, che rinominiamo SafeLampGenie:

public class SafeLampGenie {
private static SafeLampGenie instance = new SafeLampGenie();

public static SafeLampGenie getInstance() {
return instance;
}
...

Altrimenti dovremmo riscrivere il metodo getInstance implementando noi la sincronizzazione, ovvero il modo che Java ci mette a disposizione per gestire il problema della concorrenza.

Ne parliamo in un altro post, magari.

Per completare l'esempio manca la definizione della classe Human, che riduciamo ai minimi termini, come segue:

package gof.singleton;

public class Human {
private final String name;

public Human(String name) {
this.name = name;
}

public String getName() {
return name;
}
}


E quindi un funzione main che ci permetta di verificare il funzionamento di quanto abbiamo scritto:

public static void main(String[] args) {
Human tom = new Human("Tom");
Human bob = new Human("Bob");

if(SafeLampGenie.getInstance().getMaster() == null) {
System.out.println("Genie is looking for a master");
}

if(SafeLampGenie.getInstance().setMaster(tom))
System.out.println(tom.getName() + ": I'm the Genie Master!");
if(SafeLampGenie.getInstance().setMaster(bob) == false)
System.out.println(bob.getName() + ": Damn, I'm late!");
}

Consumo asincrono di messaggi

Aggiungiamo un altro client al nostro sistema. Questo consuma messaggi in maniera asincrona. In pratica lo lanciamo, mettendolo in ascolto su una Queue o Topic e lo lasciamo correre indefinitamente.

TextListener

Gran parte del codice é uguale a quello che abbiamo scritto per il client sincrono, il principale cambiamento é che la gestione dei messaggi avviene per mezzo di un MessageListener.

A noi interessa gesitire solo i messaggi di testo, quindi lo chiamiamo TextListener:

package asynchConsumer;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

public class TextListener implements MessageListener {
public void onMessage(Message message) {
System.out.println("Enter TextListener.onMessage()");
TextMessage msg = null;

try {
if(message instanceof TextMessage) {
msg = (TextMessage) message;
System.out.println("Reading message: " + msg.getText());
}
else {
System.err.println("Message is not a TextMessage");
}
}
catch(JMSException e) {
System.err.println("JMSException in onMessage(): " + e.toString());
}
catch(Throwable t) {
System.err.println("Exception in onMessage():" + t.getMessage());
}

System.out.println("Exit TextListener.onMessage()");
}
}

Consumer

La classe Consumer di asynchConsumer si differenzia da quella di synchConsumer praticamente nel solo metodo getMessage(). In questo caso ci si limita ad istanziare il Listener e associarlo al consumer:

public void getMessages() throws JMSException, NamingException {
Destination dest = getDestination();

MessageConsumer consumer = session.createConsumer(dest);
TextListener listener = new TextListener();
consumer.setMessageListener(listener);
connection.start();

System.out.println("Waiting for messages");
}

Main

Il cambiamento rilevante nella classe Main é che aggiungiamo la gestione dell'interrupt. L'idea é che il thread iniziale che esegue la funzione main termina dopo aver creato il consumer. Restano vivi i thread generati da questo, che termineremo esplicitamente su richiesta dell'utente, mediante un control-c.

La gestione dello shutdown é affidata a questa classe:

package asynchConsumer;

import javax.jms.JMSException;

public class Shutdown implements Runnable {
Consumer consumer;

public Shutdown(Consumer consumer) {
this.consumer = consumer;
}

public void run() {
System.out.println("Shutting down...");

if(consumer != null)
try { consumer.close();} catch(JMSException ex) {}
}
}

Tiene al suo interno un riferimento al consumer e, quando arriva il segnale di terminazione lo chiude.

La comunicazione alla Java Virtual Machine che va chiamato il metodo run() della nostra classe Shutdown quando arriva il momento di terminare l'esecuzione, si compie per mezzo di una chiamata a addShutdownHook() a cui passiamo un nuovo thread che ha il compito di eseguire la nostra classe.

Vediamo dunque la funzione main() della nostra applicazione con questa variazione, per il resto praticamente uguale al main per il consumer sincrono:

public static void main(String[] args) {
boolean isTopic = (args.length == 1 && args[0].startsWith("t")) ? true : false;

Consumer consumer = null;
try {
consumer = new Consumer();

// default: wait for queue messages
if(isTopic)
consumer.setDestination(Consumer.Type.TOPIC);
consumer.getMessages();
}
catch(Exception ex) {
System.err.println("Consuming failed: " + ex.toString());
ex.printStackTrace();
System.exit(1);
}

Runtime.getRuntime().addShutdownHook(new Thread(new Shutdown(consumer), "Shutdown"));
System.out.println("Control-C to terminate");
}

Template Method

Pattern descritto in Design Pattern.

Lo scopo del pattern Template Method é quello di definire la struttura di un algoritmo, lasciando che alcuni suoi passi vengano definiti dalle sottoclassi. In questo modo le sottoclassi possono ridefinire alcuni passi dell'algoritmo senza cambiarne la struttura.

Partecipanti
  • AbstractClass, definisce come astratte le operazioni primitive che sono implementate alle classi derivate e che definiscono i singoli passi dell'algoritmo; implementa il template method che definisce la struttura dell'algoritmo e che usa le funzioni che definiscono i singoli passi, come definiti nella classe astratta o nelle classi derivate;
  • ConcreteClass, implementa i singoli passi come richiesto dalla versione dell'algoritmo.
Esempio

Nella nostra applicazione fantasy, i personaggi possono andare in appositi negozi per comprare oggetti di utilità più o meno comune. La struttura dell'acquisto é sempre la stessa per ogni negozio: il negoziante valuta se il cliente é di suo gradimento, poi verifica che il prodotto selezionato sia adeguato per il cliente, ci si accorda sul prezzo e solo come ultimo passo viene dato al cliente l'ambito prodotto.

Il problema é che ogni negozio ha una diversa valutazione del cliente (alcuni vendono solo a maghi, ad esempio), ha un diverso magazzino, e un diverso listino. Insomma, vogliamo avere un algoritmo di vendita che abbia una struttura rigida, che deve essere eseguita come stabilito da tutti i negozi, ma nel contempo vogliamo anche una notevole libertà implementativa in ogni passo dell'algoritmo.

Il pattern Template Method ci aiuta a realizzare proprio questo.

AbstractClass

In cima alla gerarchia di negozi c'é la classe astratta Shop, che mette a disposizione il metodo template che qui si chiama buy() e ritorna l'acquisto fatto (o null, in caso la trattativa non vada a buon termine).

In questo caso non abbiamo una implementazione di default per nessuno dei passi dell'algoritmo definito da buy(), perciò tutti i metodi che definiscono i passi intermedi sono astratti:

package gof.templateMethod;

public abstract class Shop {
protected final String name;

public Shop(String name) {
this.name = name;
}

public String getName() {
return name;
}

public Good buy(Character character, int item, int price) {
if(checkCharacter(character) == false) {
System.out.println(name + ": I won't sell you anything");
return null;
}

if(checkGood(character, item) == false) {
System.out.println(name + ": Sorry I can't sell you that");
return null;
}

if(checkPrice(character, item, price) == false) {
System.out.println(name + ": Price is high, but quality too!");
return null;
}

return makeGood();
}

abstract protected boolean checkCharacter(Character character);

abstract protected boolean checkGood(Character character, int item);

abstract protected boolean checkPrice(Character character, int item, int price);

abstract protected Good makeGood();
}

ConcreteClass

Definiamo un solo tipo negozio in questo esempio. Si tratta dell'armeria. Una regola fondamentale della gilda degli armaioli é che solo gli umani possono comprare qui, e quindi quello che farà la checkCharacter() é assicurarsi che il cliente sia umano.
Le armerie vendono prodotti che hanno un numero di codice, stabilito dal Grande Catalogo Universale, compreso tra 1001 e 2000. Controllo che viene compiuto in checkGood(). Per alcuni prodotti potrebbe essere necessario fare altri controlli aggiuntivi, dato che alcune armi piuttosto difficili da usare potrebbero essere vendute solo a personaggi con particolari caratteristiche, per questo motivo si passa a questo metodo anche il riferimento al cliente.
Anche l'implementazione del listino prezzi é correntemente molto debole, tutte le merci disponibili sono vendute a 5 (o più), ma é prevista la possibilità di avere prezzi diversi per ogni tipo di merce venduta, come ci si dovrebbe aspettare, e anche in funzione del cliente.
Del resto il catalogo é in realtà correntemente molto limitato, dato che la makeGood() in realtà ritorna sempre l'istanza di una semplice spada.

Ecco a seguire la classe che implementa le nostre armerie:

package gof.templateMethod;

public class Armory extends Shop {
public Armory(String name) {
super(name);
}

@Override
protected boolean checkCharacter(Character character) {
if(character instanceof Human)
return true;

return false;
}

@Override
protected boolean checkGood(Character character, int item) {
if(item > 1000 && item < 2000)
return true;
return false;
}

@Override
protected boolean checkPrice(Character character, int item, int price) {
if(price >= 5)
return true;
return false;
}

@Override
protected Good makeGood(int item) {
System.out.println(name + ": here is your Simple Sword!");
return new SimpleSword();
}
}

Classi al contorno

Resta da definire la gerarchia di classi dei clienti, di cui diamo una definizione minimale così:

public interface Character {
abstract public String getName();
}

class Human implements Character {
private final String name;

public Human(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

class Monster implements Character {
private final String name;

public Monster(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

Ed ecco la gerarchia di oggetti commerciabili:

public interface Good {
public int getPrice();
}

public interface Weapon extends Good {
public int getOffensiveBonus();
public int getDefensiveBonus();
}

public class SimpleSword implements Weapon {
private int price = 10;
private int offensiveBonus = 12;
private int defensiveBonus = 6;

public int getPrice() {
return price;
}

public int getOffensiveBonus() {
return offensiveBonus;
}

public int getDefensiveBonus() {
return defensiveBonus;
}
}

Main

Resta solo da scrivere un piccolo test client:

public static void main(String[] args) {
Shop shop = new Armory("Kill Safe");

System.out.println("Tom wants a sword from " + shop.getName());

Character tom = new Monster("Tom");
Good good = shop.buy(tom, 1001, 5);
if(good == null) {
System.out.println("Tom goes somewhere else.");
}

System.out.println("Tom wants a something from " + shop.getName());
Character tim = new Human("Tim");
good = shop.buy(tim, 1, 100);

if(good == null) {
System.out.println("Tim looks for a sword");
}
good = shop.buy(tim, 1001, 4);

if(good == null) {
System.out.println("Tim increases his offer");
}
good = shop.buy(tim, 1001, 5);

if(good instanceof Weapon) {
Weapon w = (Weapon)good;

System.out.println("The offensive bonus is " + w.getOffensiveBonus());
}
}

Che produce questo output:

Tom wants a sword from Kill Safe
Kill Safe: I won't sell you anything
Tom goes somewhere else.
Tom wants a sword from Kill Safe
Kill Safe: Sorry I can't sell you that
Tim looks for something else
Kill Safe: Price is high, but quality too!
Tim increases his offer
Kill Safe: here is your Simple Sword!
The offensive bonus is 12

Strategy

Pattern descritto in Design Pattern.

Lo scopo del pattern Strategy (aka Policy) é quello di definire una famiglia di algoritmi, incapsularli e renderli intercambiabili. Con l'uso di Strategy é possibile rendere l'algoritomo variabile in modo indipendente dall'oggetto che lo usa.

Partecipanti
  • Strategy: dichiara una interfaccia comune a tutti gli algoritmi supportati.
  • ConcreteStrategy: implementa gli algoritmi usando l'interfaccia Strategy.
  • Context: configurato con un oggetto di tipo ConcreteStrategy, mantiene un riferimento all'interfaccia Strategy, può definire un interfaccia per l'accesso di Strategy ai suoi dati.

Esempio

Nella nostra applicazione fantasy i personaggi spesso combattono fra loro. Il nostro problema é che esistono diversi modi di combattere, alcuni brutti ceffi fanno ricorso quasi esclusivamente alla forza bruta, altri, che possono essere non meno pericolosi, integrano la loro forza, solitamente limitata, con l'uso della loro astuzia, altri ancora, sapienti maghi, se provocati utilizzano le loro ben allenate facoltà mentali, e solo marginalmente la propria forza e abilità per causare danni al loro oppositore. Insomma, ogni carattere ha la sua diversa strategia nel combattimento.

E questo non é tutto. Ne succedono di tutti i colori in quel mondo, può persino accadere che un valoroso guerriero decida di abbandonare l'uso delle armi e mettersi a praticare la magia. Ovvero, un carattere non ha una strategia di combattimento immutabile, al contrario questa può cambiare nel corso del tempo.

Contesto astratto

I nostri personaggi saranno umani, mostri, e chissà che altro. Modifichiamo quindi leggermente il pattern inserendo una classe astratta Character che ne sintetizza le qualità comuni e fornisce una referenza alla strategia di combattimento:

package gof.strategy;

public abstract class Character {
protected String name;
protected int force;
protected int intelligence;
protected int ability;

protected CombatStrategy combat;

public Character(String name, int force, int intelligence, int ability) {
this.name = name;
this.force = force;
this.intelligence = intelligence;
this.ability = ability;
}

public void setCombatStrategy(CombatStrategy combat) {
this.combat = combat;
}

public void fight() {
System.out.print("For fighting " + name + " uses the ");
System.out.println(combat.getClass().getSimpleName() + ": ");

combat.fight();
}
}

Il cuore del pattern é nel fatto che il modo di combattere é definito da una gerarchia di classi che ha origine nell'interfaccia CombatStrategy, e il Context usa una ConcreteStrategy non derivando da essa ma possedendo un'istanza della classe. In termini Object-Oriented: il Character non é, ma puttosto ha una CombatStrategy.
Questo ci permette di cambiare facilmente il modo di combattere del nostro personaggio, basta usare il metodo setCombatStrategy() che aggiorna la sua strategia di combattimento.
Quindi il metodo fight() semplicemente delega l'attività alla CombatStrategy corrente.

Contesto concreto

Gli umani entrano in gioco, nel nostro mondo fantasy, con una certa propensione alla violenza. Questo viene descritto dal costruttore della classe, che gli assegna come strategia di combattimento iniziale quella propria di guerriero brutale:

package gof.strategy;

public class Human extends Character {
public Human(String name, int force, int intelligence, int ability) {
super(name, force, intelligence, ability);

combat = new BrutalWarriorStrategy(this);
}
}

Strategia

Nella versione iniziale la nostra strategia é una interfaccia molto semplice, mettendo a disposizione solo il metodo fight(), che permette di combattere:

package gof.strategy;

public interface CombatStrategy {
public void fight();
}


Strategie concrete

Vediamo un paio di possibili strategie che implementano la nostra interfaccia, cominciando dal default per gli umani, il guerriero brutale:

package gof.strategy;

public class BrutalWarriorStrategy implements CombatStrategy {
private Character character;

public BrutalWarriorStrategy(Character character) {
this.character = character;
}

public void fight() {
System.out.println("90% force: " + character.force * 0.90);
System.out.println("5% ability: " + character.ability * 0.05);
System.out.println("5% intelligence: " + character.intelligence * 0.05);
System.out.println("---");
}
}

Quello che cambia, rispetto alla strategia del mago, che vediamo a seguire, e solo nel modo in cui vengono gestite le caratteristiche del carattere che sua la strategia:

package gof.strategy;

public class WizardStrategy implements CombatStrategy {
private Character character;

public WizardStrategy(Character character) {
this.character = character;
}

public void fight() {
System.out.println("10% force: " + character.force * 0.1);
System.out.println("20% ability: " + character.ability * 0.2);
System.out.println("70% intelligence: " + character.intelligence * 0.7);
System.out.println("---");
}
}

Main

Ci resta da scrivere un piccolo tester, che crea un umano, ne verifica il modo di combattere, gli cambia la strategia e vede come ciò impatta sul di lui:

public static void main(String[] args) {
Character tom = new Human("Tom", 50, 40, 30);

tom.fight();

CombatStrategy combat = new WizardStrategy(tom);
tom.setCombatStrategy(combat);

tom.fight();
}

Questo l'output che ci attendiamo:

For fighting Tom uses the BrutalWarriorStrategy:
90% force: 45.0
5% ability: 1.5
5% intelligence: 2.0
---
For fighting Tom uses the WizardStrategy:
10% force: 5.0
20% ability: 6.0
70% intelligence: 28.0
---