1.2 Hello World!
5.2.6 BlackJack
⤷
return true;
return next.chiediCarta();
} }
5.2.6 BlackJack
Gestire lo svolgimento di una partita con i tre sfidanti
Inizialmente dobbiamo inizializzare le strategie dei tre sfidanti. Ad esempio, con le strategie precedentemente definite:
mattia.setStrategia(new StrategiaBerserk(mattia, Strategia.STARE));
carlo.setStrategia(new StrategiaPrudente(carlo, new RandomStrategy(Strategia.STARE)));
violetta.setStrategia((new StrategiaGuardaBanco(violetta, banco, Strategia.STARE)));
Poi, dopo aver aggiunto i tre sfidanti alla lista, diamo le carte iniziali agli sfindanti e infine al banco.
sfidanti.add(carlo);
sfidanti.add(mattia);
sfidanti.add(violetta);
for (Sfidante sfidante : sfidanti) { sfidante.carteIniziali();
}banco.carteIniziali();
A questo punto, facciamo giocare ogni sfidante e alla fine di ognuno incrementiamo un contatore se verifichiamo che è sballato.
int sballati = 0;
for (Sfidante sfidante : sfidanti) { sfidante.gioca();
if (sfidante.isSballato()) sballati++;
}
Per ultima cosa, nel caso non tutti gli sfidanti siano sballati, gioca anche il banco.
if (sballati < sfidanti.size()) banco.gioca();
Rimane da fare un’ultima semplice modifica alle classi
Sfidante
eMazziere
in modo che le stampe successive producano un output significativo.Infatti l’istruzione
System.out.println(sfidante);
richiamasfidante.toString()
che non è stato ridefinito e quindi corrisponde ancora alla implementazione di default data a livello di Object: la stampa del nome della classe e del suo indirizzo in memoria.È presente però già a livello di interfaccia
GiocatoreBJ
una implementazione di default del metodoasString()
a cui possiamo delegare il compito.Aggiungiamo quindi sia nella casse
Sfidante
che nella classeMazziere
il seguente codice:5.2 Soluzioni
@Override
public String toString() { return asString();
}
6 Lab06: Musica Maestro!
Il punto di partenza di questo laboratorio è disponibile all’indirizzo: https://gitlab.com/
programmazione2/lab07-musica-maestro.
6.1 Esercizi
Obiettivo dell’esercizio è progettare e realizzare un insieme di classi che consentano di simulare un insieme di strumenti musicali, seguendo passo passo le specifiche di seguito riportate.
Prima di passare a uno step successivo consigliamo di assicurarsi con semplici test (usando JUnit) che quanto scritto funzioni.
6.1.1 Obiettivi Step 1
Implementare due diverse classi che rappresentano due diversi strumenti musicali:
Trumpet
eHorn
. Entrambe le classi implementano l’interfacciaMusicalInstrument
e rispondono alla chiamata del metodopublic @NotNull String play()
che restituisce nel primo caso la stringa"pepepe"e nel secondo la stringa"papapa".
Step 2
Implementare altre due classi
WaterGlass
eIronRod
. Esse, pur non implementando l’interfacciaMusicalInstrument
hanno un comportamento molto simile:WaterGlass
possiede un metodo public @NotNull String tap()che restituisce la stringa"diding";
IronRod
invece aderisce alla interfacciaGermanPercussiveInstrument
e risponde quindi alla chiamatapublic @NotNull String spiel() restituendo la stringa"tatang".
Step 3
Aggiungere la possibilità di usare istanze della classe
WaterGlass
e delle classi aderenti alla inter-facciaGermanPercussiveInstrument
(e in particolare perciò della classeIronRod
) come og-getti di tipoMusicalInstrument
. Tale obiettivo deve essere raggiunto usando il design pattern denominato Adapter; più in dettaglio, è richiesta la realizzazione di due diversi Adapter:Wate
⌋rGlassInstrument
che realizza un Class adapter eGermanMusicalInstrument
che realizza invece un Object adapter.Step 4
Creare una classe che rappresenti un’orchestra di oggetti di tipo
MusicalInstrument
. Tale obiettivo deve essere raggiunto usando il design pattern denominato Composite; più in dettaglio, è richiesta la realizzazione di una classeOrchestra
che deve implementare l’interfacciaMusi
⌋calInstrument
e deve rispondere all’invocazione del metodoString
play()demandando la chiamata agli oggettiMusicalInstrument
aggregati e inserendo degli a capi tra l’uno e l’altro.Gli oggetti che compongono il gruppo orchestrale vengono passati all’interno di una
Collec
⌋tion
direttamente al costruttore della classeOrchestra
:public Orchestra(@NotNull Collection<MusicalInstrument> instruments)
Step 5
È necessario avere di ogni strumento una versione che suoni fortissimo (cioè il cui suono sia scritto tutto in lettere maiuscole).
Per evitare un proliferare di nuove classi, tale obiettivo deve essere raggiunto usando il design pattern
Decorator
. Più in dettaglio, è richiesta la realizzazione di una classeFortissimo
che implementa l’interfacciaMusicalInstrument
e decora un’istanza diMusicalInstrument
in modo che il suono prodotto dall’elemento decorato risulti tutto in maiuscolo.Step 6
È necessario avere di ogni strumento una versione che suoni lentamente (cioè le cui vocali vengano triplicate), e anche in questo caso deve essere realizzato tramite pattern
Decorator
.Visto che ci sono chiaramente delle parti di codice in comune (specifiche del pattern) con la classe sviluppata al punto 6.1.1, prima di procedere alla scrittura di questa classe, si deve modificare la classe
Fortissimo
facendola ereditare da una nuova classeAbstractDecorator
che appunto fattorizzi il codice comune.È richiesta quindi la realizzazione di una classe
Slow
che estenda la classeAbstractDecor
⌋ator
e decora un’istanza diMusicalInstrument
in modo che il suono prodotto dall’elemento decorato risulti con le vocali triplicate.Nella classe astratta si consiglia di cercare un template di decorazione parametrico rispetto a un metodo astratto
public @NotNull String transformEffect(@NotNull String sound) che sarà l’unica cosa che dovranno realizzare i decoratori concreti.
6.2 Soluzioni
Step 1: Implementare due diverse classi che rappresentano due diversi strumenti: Trumpet e Horn. Entrambe le classi implementano l’interfaccia MusicalInstrument e rispondono alla chiamata del metodoString play()che restituisce nel primo caso la stringa"pepepe"e nel secondo la stringa"papapa".
Dopo aver creato con IntelliJ IDEA un nuovo progetto Java (di tipo Gradle, vedi Capitolo1.2), possiamo aggiungere le classi richieste in unpackage
it.unimi.di.prog2.music
. Il metodoplay
, che è comune ad entrambe, è ciò che definisce l’interfacciaMusicalInstrument
, cheTrumpet
eHorn
implementano in modi differenti. È utile procedere con un approccio test driven, cioè descrivendo prima l’obiettivo che vogliamo raggiungere con un test di unità (che6.2 Soluzioni
inizialmente fallirà), scrivendo poi il codice che permette al test di passare e infine valutando se esistono possibilità di riorganizzare (con opportune azioni di refactoring) il codice in maniera più elegante o generale, conservando la corretta funzionalità del test. Questo ciclo che si ripete e che caratterizza l’approccio TDD, è ben riassunto dallo slogan “red-green-refactoring”, dove i colori ricordano l’esito del test come spesso vengono segnalati dagli IDEs.Usando JUnit4 il primo test potrebbe essere:
package it.unimi.di.prog2.music;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class MusicTest {
@Test
public void testTrumpet() {
MusicalInstrument instrument = new Trumpet();
assertEquals("pepepe", instrument.play());
} }
Si noti che inizialmente nemmeno compila, perché i simboli
MusicalInstrument
,Trumpet
eplay
non sono definiti. Possiamo però definirli velocemente facendoci aiutare dall’IDE (per esempio con Create interface; attenzione a crearle in src main java e non src test java).La prima esecuzione del test è bene che provochi un fallimento (red): è la garanzia che stiamo introducendo una nuova funzionalità e non codice che non viene stimolato dal test. Per questo motivo
play
ritornanull.// MusicalInstrument.java package it.unimi.di.prog2.music;
public interface MusicalInstrument { String play();
}
// Trumpet.java
package it.unimi.di.prog2.music;
public class Trumpet implements MusicalInstrument {
@Override
public String play() { return null;
} }
Ora possiamo lanciare il task check di Gradle e ottenere un test fallito.
expected:<pepepe> but was:<null>
Expected :pepepe Actual :null
Il fallimento si risolve facilmente (green!) e non si intravvedono possibilità di refactoring.
public class Trumpet implements MusicalInstrument {
@Override
public String play() { return "pepepe";
} }
Procediamo quindi con un nuovo test e un nuovo ciclo “red-green-refactoring”.
@Test
public void testHorn() {
MusicalInstrument instrument = new Horn();
assertEquals("papapa", instrument.play());
}
Ciò porta (dopo il fallimento iniziale) alla classe
Horn
funzionante.package it.unimi.di.prog2.music;
public class Horn implements MusicalInstrument {
@Override
public String play() { return "papapa";
} }
Potremmo operare un piccolo refactoring nei test, per enfatizzare che gli oggetti
Trumpet
eHorn
sono entrambi deiMusicalInstrument
: i loro tipi, cioè, hanno una relazione is a con il tipoMusicalInstrument
.public class MusicTest {
private MusicalInstrument instrument;
@Test
public void testTrumpet() { instrument = new Trumpet();
assertEquals("pepepe", instrument.play());
}
@Test
public void testHorn() { instrument = new Horn();
assertEquals("papapa", instrument.play());
} }
Step 2: Implementare altre due classiWaterGlasseIronRod. Esse, pur non implementando l’interfacciaMusicalInstrumenthanno un comportamento molto simile: WaterGlasspossiede un metodo String tap() che restituisce la stringa "diding"; IronRod invece aderisce alla interfacciaGermanPercussiveInstrumente risponde quindi alla chiamataString spiel() restituendo la stringa"tatang".
Scriviamo subito un primo test.
@Test
public void testWaterGlass() {
6.2 Soluzioni
WaterGlass wg = new WaterGlass();
assertEquals("diding", wg.tap());
}
Per superarlo (non dimentichiamo di farlo prima fallire, però!) scriveremo la classe
WaterG
⌋lass
.package it.unimi.di.prog2.music;
public class WaterGlass { public String tap(){
return "diding";
} }
E per
IronRod
:@Test
public void testIronRod() {
GermanPercussiveInstrument gpi = new IronRod();
assertEquals("tatang", gpi.spiel());
}
package it.unimi.di.prog2.music;
public class IronRod implements GermanPercussiveInstrument {
@Override
public String spiel() { return "tatang";
} }
Step 3: Aggiungere la possibilità di usare istanze della classeWaterGlasse delle classi ade-renti alla interfaccia GermanPercussiveInstrument (e in particolare perciò della classe Ir⌋
onRod) come oggetti di tipoMusicalInstrument. Tale obiettivo deve essere raggiunto usando il design pattern denominato Adapter; più in dettaglio, è richiesta la realizzazione di due diversi Adapter: WaterGlassInstrument che realizza un Class adapter e GermanMusicalInstrum⌋
ent che realizza invece un Object adapter.
Iniziamo a tradurre il primo obiettivo in un test.
@Test
public void testWaterGlassInstrument() { instrument = new WaterGlassInstrument();
assertEquals("diding", instrument.play());
}
Per soddisfare il test con un “Class adapter”,
WaterGlassInstrument
, oltre a implementare l’interfacciaMusicalInstrument
, erediterà anche le funzionalità diWaterGlass
e useràtap()
per realizzareplay()
.public class WaterGlassInstrument extends WaterGlass implements MusicalInstrument {
@Override
public String play() { return tap();
} }
GermanMusicalInstrument
è simile, ma invece di ereditare la funzionalità, delega a un oggetto di tipoIronRod
la sonata.@Test
public void testGermanMusicalInstrument() {
instrument = new GermanMusicalInstrument(new IronRod());
assertEquals("tatang", instrument.play());
}
public class GermanMusicalInstrument implements MusicalInstrument { private GermanPercussiveInstrument delegate;
public GermanMusicalInstrument(GermanPercussiveInstrument gpi) { delegate = gpi;
}
@Override
public String play() { return delegate.spiel();
} }
Si noti che il costruttore di
GermanMusicalInstrument
in generale richiede unGermanPe
⌋rcussiveInstrument
, non necessariamente unIronRod
. Step 4, 5, 6:...TBD
7 Lab07: Musica Maestro (con gli spettatori)!
Il punto di partenza di questo laboratorio è disponibile all’indirizzo: https://bitbucket.org/
prog2unimi/p2-lab07-2019.