Problema
Quindi sto realizzando un videogioco e sto affrontando un problema di progettazione. Vorrei passare il riferimento di una classe che viene costruita a un costruttore di un altro oggetto e passare questo altro nuovo oggetto al super costruttore della prima classe.
Fondamentalmente qualcosa del genere:
class B extends A {
B() {
super(new C(this));
}
}
Contesto
Quindi ho una classe World
che rappresenta il mondo del mio videogioco. Questo mondo può essere giocato e visualizzato da un WorldScreen
. Non voglio che World
si basi su qualsiasi WorldScreen
. Sarebbe un cattivo design per un mondo dipendere da uno schermo da riprodurre, al mondo non importa quale sia la tecnologia utilizzata per rendere le sue entità e non interessa nemmeno l'interfaccia utente. Quindi, invece di avere il mondo che chiama uno dei metodi di WorldScreen
quando il giocatore muore, per esempio, gli forniamo un WorldEventHandler
(modello di osservatore ma solo un osservatore). Quella WorldEventHandler
si prenderà cura di mantenere un riferimento allo schermo per visualizzare la schermata della morte quando il giocatore muore o lo schermo finale ecc.
class World { //a video game world
//notifies about events going on in the world (e.g. the player's death)
final WorldEventHandler handler;
World(WorldEventHandler handler) {
this.handler = handler;
}
}
WorldScreen
crea un'istanza di uno specifico WorldEventHandler
che interagirà con se stesso.
class WorldScreen {
final World world;
WorldScreen() {
//all good, WorldEventHandlerImpl will interact with WorldScreen whenever needed (display death screen?)
world = new World(new WorldEventHandlerImpl(this));
}
}
Questo design è tutto perfetto per me (ancora aperto per miglioramenti), World
non dipende da nient'altro che un'interfaccia per ascoltare i suoi eventi e WorldScreen
è in grado di personalizzare il suo comportamento con un'implementazione specifica di quel gestore di eventi.
Il problema si presenta quando provo a fare ereditarietà con WorldScreen
. Esistono molte diverse implementazioni di WorldScreen
che fanno cose leggermente diverse ma molte cose in comune raggruppate nella classe WorldScreen
astratta.
abstract class WorldScreen {
final World world;
WorldScreen(WorldEventHandler handler) {
//fine too
world = new World(handler);
}
}
Tuttavia, come puoi vedere il WorldEventHandler
deve essere specificato dalla sottoclasse. Spetta alla sottoclasse decidere cosa fare con gli eventi mondiali. Quindi l'errore si verifica quando provo ad instanciare un WorldEventHandler
con un riferimento allo schermo in una sottoclasse:
class OnlineWorldScreen extends WorldScreen {
OnlineWorldScreen() {
super(new OnlineWorldEventHandler(this)); //cannot reference 'this' before supertype constructor has been called
}
}
Soluzioni alternative
Ho pensato a 2 soluzioni alternative, ma odio entrambe.
-
Dopo aver impostato il riferimento allo schermo con un metodo impostato:
- Interrompe il modello di tutti i miei
WorldEventHandler
per un problema di costruttore - Farà molto codice dupplicated poiché ogni implementazione richiederà un metodo set con un tipo diverso da impostare.
- Il campo non doveva essere inizialmente modificato e non ho altri motivi per consentire agli altri programmatori di pensare che potrebbe essere un campo che verrà modificato
- Interrompe il modello di tutti i miei
-
Creazione di un abstract
getEventHandler()
obuildEventHandler()
inWorldScreen
per far sì che il super costruttore lo sviluppi durante la creazione del mondo.- Quello che doveva essere un parametro costruttore ora è un metodo
- Questo metodo è pensato per essere usato una sola volta e in pratica non fa altro che una chiamata a un costruttore
- Rende le implementazioni di
WorldScreen
più pesanti
Domanda
Esistono soluzioni alternative migliori? Se no, qual è il migliore e perché? Se il mio design è imperfetto e potrebbe essere modificato in uno senza quel problema, ti prego di dirmi come invece di presentare una soluzione alternativa.
Modifica
Per rispondere al commento di Sebastian Redl, sì, è logico che lo schermo crei il mondo. Una schermata è lo stato attuale del gioco, gli esempi tipici sono MainMenuScreen, SettingsScreen, LoginScreen ecc. Quindi, quando vai da MainMenuScreen a OnlinePlayScreen, crea il mondo e lo riproduce dal tuo input. Non ha senso avere più schermi su un singolo mondo poiché è impossibile avere più schermi contemporaneamente nell'applicazione (usando il framework LibGDX). La ragione per cui voglio che il mondo sia al 100% standalone è di poterne eseguire uno su un server per verificare se un giocatore sta barando o meno. (Prendendo i suoi input e controllando se raggiunge lo stesso tempo sul server che il suo cliente ha fatto finta di).
Potrei fare invece una lista di osservatori (non che ne abbia bisogno) ma, se lo facessi, farei un'implementazione speciale di WorldEventHandler
che può ricevere più ascoltatori. È molto più semplice per World
(che è già pieno di motore fisico e logica di gioco) per chiamare un metodo da un oggetto piuttosto che eseguire il ciclo in un elenco di 1 elementi ogni volta.