Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
vuoi
o PayPal
tutte le volte che vuoi
SpecialItem e di far visualizzare le statistiche dell’utente; il Controller
inoltre, è responsabile di far partire la View, di inviarle informazioni
per gestire la musica e di inviarle la lunghezza (in numero di caselle)
9
della scacchiera dello scenario attualmente utilizzato.
Per finire, si può osservere nello schema la presenza di una classe
Application, essa incapsula il Controller permettendo di avviare
l’applicazione.
Figura 2.2: Schema UML rappresentante le interazioni tra View e
Controller. 10
2.2 Design dettagliato
Barilari Nicolas (Model)
Per quanto riguarda la parte di Model dell’applicazione, della
quale sono il solo autore e responsabile, ho scelto di adottare le
seguenti decisioni di progettazione ed i seguenti pattern:
o Memento Pattern
La classe ModelImpl, che è l’unica ad implementare
l’interfaccia Model e che rappresenta il cuore del dominio
dell’applicazione, è ricca di campi ed è soggetta a continui
mutamenti dovuti alla frequente richiesta di modifica dei
parametri delle entità in gioco. L’unica cosa certa è che
prima o poi potrà essere richiesto di tornare ai parametri
iniziali di default (avviare una partita o riavviarne una in
corso). Per questi motivi ho deciso di usare il pattern
Memento: il ModelImpl crea con createMemento() ad
inizio partita un’istantanea parziale del suo stato interno
(un ModelMemento) e la passa in ingresso al metodo
addMemento() della classe CareMementoTaker, la quale
incapsula al suo interno il memento appena creato.
Quando il ModelImpl ha bisogno di ripristinare il suo
stato interno di default (cioè di inizio partita) chiama il
metodo getMemento(), che gli restituisce il memento
creato in precedenza, dal quale ModelImpl può trarre
tutte le informazioni significative tramite metodi getter.
CareMementoTaker è responsabile solo della custodia del
memento e non opera mai su di esso né ne esamina il
contenuto. Il seguente schema UML mostra quanto
descritto. 11
o Decorator pattern
Il videogioco rende disponibili tre diverse tipologie di
dado: un dado classico (classe ClassicDice) che non è altro
che un dado normale a sei facce dall’1 al 6, un dado
“amplificato” (classe Dice5To10) che ha sempre sei facce
ma dal 5 al 10 ed infine un dado con numeri anche
negativi (classe NegativeDice), il quale presenta sette
facce dal -2 al 5 (0 escluso). Ho ben presto notato la
notevole somiglianza tra questi dadi, il chè mi ha fatto
optare per il pattern Decorator: Dice5To10 e NegativeDice
si compongono di un ClassicDice, decorandolo e
richiedendolo sin dal momento della loro costruzione;
aggiungono perciò dinamicamente funzionalità aggiuntive
al dado classico.
o Factory Method pattern
Ho scelto di creare i dadi di gioco tramite una
factory la cui interfaccia ho denominato
DiceFactory; essa crea oggetti aderenti
all’interfaccia Dice, cioè dadi. Da ricordare che
quando crea Dice5To10 e NegativeDice passa ai loro
costruttori un ClassicDice (Decorator pattern
discusso sopra).
12
Data la complessità di costruzione dello scenario di
gioco, ho deciso di utilizzare anche in questo caso
una factory, disaccoppiando perciò la logica d’uso
degli oggetti che estendono l’interfaccia Scenery
dalla logica di costruzione di tali oggetti e
demandando quest’ultima ad una classe apposita
che ho chiamato SceneryFactoryImpl (che
implementa l’interfaccia SceneryFactory).
SceneryFactoryImpl crea uno scenario partendo da
informazioni sotto forma di lista di interi e ritorna
lo scenario stesso; esso può essere manipolato in
vario modo con alcuni setter e getter, come
mostrato nel seguente schema UML.
13
o Template Method Pattern
Ho utilizzato il pattern Template Method per modellare il
concetto di oggetto speciale raccoglibile negli scenari di
gioco. Ho creato quindi un’interfaccia SpecialItem che
rappresenta un qualsiasi oggetto speciale di cui sopra, la
quale viene implementata da una classe astratta
IntegerReturningItem che modella solo oggetti speciali i
quali se raccolti ritornano un numero intero. Inserendo
l’interfaccia SpecialItem ho dunque progettato tenendo
conto della possibile necessità futura che vengano inseriti
oggetti speciali con qualunque tipo di effetto o valore
restituito. La classe astratta IntegerReturningItem ha, tra
gli altri, due metodi astratti (due template method in
questo caso, isVisible() e getType()) che andranno perciò
implementati nelle sottostanti classi concrete, in questo
caso Coin, Diamond e Skull. Le dinamiche appena
descritte sono illustrate nello schema qui sotto.
14
o Strategy Pattern
Ho utilizzato il pattern Strategy quando mi sono ritrovato
ad avere tutti e tre gli oggetti Coin, Diamond e Skull che
necessitavano di esser generati dalla classe ModelImpl
sullo scenario di gioco; mi sono accorto che essi devono
tutti seguire un algoritmo di generazione praticamente
per la maggior parte identico fra loro, dunque ho adottato
uno Strategy per esternare in una classe apposita
chiamata ItemsGeneratorImpl (che implementa
ItemsGenerator) l’algoritmo di generazione di un
generico item (= oggetto speciale) e renderlo adattabile al
tipo di item concreto in quel momento richiesto. Come
suggerisce il seguente schema, l’ultimo argomento in
ingresso del metodo tryGenerateItem() è proprio il tipo di
item, che determina l’algoritmo la generazione di quello
specifico item piuttosto che di un altro.
o Builder Pattern (con ‘fluent’ interface)
Per la creazione delle statistiche dell’utente attualmente
loggato, ho deciso di creare una classe StatisticImpl (che
15
implementa l’interfaccia Statistic) al cui interno ho
incapsulato una inner class StatisticBuilder che ha il
compito di costruire pezzo per pezzo le statistiche
dell’utente. Ho adottato il pattern Builder per separare la
logica di costruzione da quella d’uso della classe
StatisticImpl e per far sì che sia possibile costruire
statistiche anche parziali, perciò con valori significativi e
altri non presenti (cioè non costruiti, dunque di valore 0,
di default). I metodi per costruire le statistiche inoltre,
come è evidenziato nello schema UML qui sotto, ritornano
il builder stesso, perciò permettono di costruire con un
approccio fluente.
o Singleton Pattern
Ho adottato ampiamente il pattern Singleton,
ogniqualvolta ho ritenuto uno spreco inutile o peggio
ancora un danno avere più istanze di uno stesso oggetto.
Le mie classi che seguono questo pattern sono: UserImpl,
UserStatisticsFileWriter, UserLogin, LanguageLoader,
SceneryDataManager, SceneryImpl, ItemsGeneratorImpl,
ClassicDice e CareMementoTaker.
16
o Suddivisione in package
Poiché il Model incapsula tutte le entità principali
dell’applicazione, ho scelto di suddividere le varie classi e
interfacce in package a seconda della loro appartenenza o
relazione con una determinata entità. Quindi ho creato un
package apposito per i dadi (che comprende anche la
factory dei dadi), un altro per gli scenari (che comprende
anche la factory degli scenari), un altro ancora per i
giocatori e così via per tutte le entità di gioco presenti nel
Model. Ciò facilita il reperimento di determinate classi o
interfacce al momento del bisogno.
o Accesso a file esterni
Mi sono inoltre occupato della maggior parte delle classi,
interfacce ed enumerazioni contenute nel package
utilities, il quale rappresenta l’unico package nel quale
tutti e tre abbiamo lavorato, chi più chi meno. Io ho
implementato: LanguageLoader, SceneryDataManager,
StatisticImpl, UserLogin, UserStatisticsFileWriter,
Statistic, Difficulty, Jump, Language, TypesOfDice e
TypesOfItem. Tra queste, ritengo sia interessante
soffermarmi a descrivere il funzionamento di quelle classi
che accedono a file esterni:
la classe UserLogin, ad ogni login, controlla se
esiste già nella home directory dell’utente una
cartella di nome .snakelad. Se non esiste, la crea e
ci genera all’interno un file di proprietà (file
.properties) col nome che l’utente ha digitato
durante il login ed informazioni di default sulle sue
statistiche. Se invece tale cartella esiste già, ci
entra dentro andando alla ricerca di un file di
proprietà che si chiami col nome digitato
dall’utente durante il login. Una volta trovato il file
giusto, lo apre e controlla che il contenuto
(chiavi/valori) sia formalmente corretto; qualora
trovi anche solo una lettera in più o in meno del
17
dovuto, la mancanza o l’aggiunta di una chiave o di
un valore non permesso, cancella il file e lo ricrea
da capo con le informazioni di default. Una volta
creato il file di default o caricato uno esistente
giudicato idoneo, utilizza le informazioni al suo
interno (statistiche utente) per modellare l’entità
User.
La classe UserStatisticsFileWriter si comporta in
modo analogo alla classe UserImpl, ma adotta
l’approccio inverso: invece di andare a leggere
informazioni sulle statistiche dell’utente, le va a
scrivere, per renderle permanenti, all’interno del
file di proprietà giusto. Questo accade
ogniqualvolta l’utente termini una partita in
modalità Single player.
La classe LanguageLoader utilizza anch’essa file di
proprietà (file .properties), però presenti nella
cartella di risorse (res directory) del progetto.
Questa classe utilizza un ResourceBundle per
andare a caricare dal file giusto la mappa di
traduzioni chiave/valore opportuna (in base alla
lingua richiesta dall’utente in quel preciso
momento).
Infine, la classe SceneryDataManager ha il compito
di andare a leggere dentro la cartella di risorse il
file di testo giusto, contenente le informazioni sullo
scenario appena scelto dall’utente. Queste
informazioni includono il numero di caselle dello
scenario e la posizione di ogni singolo serpente e
scala sul suddetto. L’ho implementata in modo che
si adatti al variare del numero di informazioni
contenute nei file. Se in futuro si volesse
aggiungere uno scenario in più, basterebbe creare
un nuovo file di testo con le informazioni giuste e la
classe SceneryDataManager sarebbe in grado di
elaborarle producendo i risultati corretti.
18
Gattucci Sofia (Controller)
La parte del Controller, funzionando da collegamento diretto tra
Model e View, è stata progettata ed implementata cercando di
mantenere una semplicit&agrav