Soluzioni alternative per mettere il riferimento in un oggetto passato come parametro al genitore

2

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.

  1. 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
  2. Creazione di un abstract getEventHandler() o buildEventHandler() in WorldScreen 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.

    
posta Winter 13.08.2017 - 17:11
fonte

4 risposte

3

Dovresti trattare OnlineWorldEventHandler come dipendenza da OnlineWorldScreen e passarlo. Il tuo problema viene immediatamente risolto semplicemente da OnlineWorldScreen prendendo handler come parametro. Ottieni anche flessibilità non legando OnlineWorldScreen a OnlineWorldEventHandler . Tuttavia, questo semplicemente spingerà il problema in un punto diverso del codice, ma fortunatamente un punto in cui è più trattabile da gestire. Ora il problema si presenta quando provi a creare OnlineWorldScreen e OnlineWorldEventHandler . Per costruirne uno, è necessario l'altro.

 OnlineWorldEventHandler handler = new OnlineWorldEventHandler(???);
 OnlineWorldScreen screen = new OnlineWorldScreen(handler);

La soluzione tipica ai problemi di dipendenza circolare (oltre alla riprogettazione di non avere dipendenze circolari) è una valutazione lazy. Ecco un approccio:

class LazyWorldEventHandler implements WorldEventHandler {
    private Function<WorldScreen, WorldEventHandler> _factory;
    private WorldHandler _handler = null;
    public LazyWorldEventHandler(Function<WorldScreen, WorldEventHandler> handlerFactory) {
        _factory = handlerFactory;
    }
    public void initialize(WorldScreen screen) {
        if(_handler != null) throw new Error("Double initialization");
        _handler = _factory(screen);
        _factory = null;
    }
    // ... implement WorldEventHandler and delegate to _handler
}

Puoi facilmente creare una versione generica di questo. Probabilmente sarebbe (e sarà) più pulito. Ora in qualche metodo di fabbrica:

LazyWorldEventHandler handler = 
    new LazyWorldEventHandler((WorldScreen screen) -> new OnlineWorldEventHandler(screen));
OnlineWorldScreen screen = new OnlineWorldScreen(handler);
handler.initialize(this);

Modifica come appropriato.

    
risposta data 13.08.2017 - 20:02
fonte
0

Nel tuo costruttore di WorldScreen , puoi impostare manualmente il WorldScreen di implementazione di this sul gestore:

WorldScreen(WorldEventHandler handler) {
    handler.setWorldScreen(this);
    world = new World(handler); 
}

E poi nell'implementazione di WorldScreen chiameresti semplicemente il costruttore genitore con il gestore di eventi appropriato:

OnlineWorldScreen() {
    super(new OnlineWorldEventHandler());
}
    
risposta data 13.08.2017 - 17:33
fonte
0

Lancio semplicemente i miei 2 centesimi qui, se dipendesse da me userei lo schema di progettazione dell'osservatore ( link ) dove World (subject) avrebbe dispatcher di eventi e invierà eventi quando succederebbe qualcosa (giocatore death, end game). Questo dispatcher di eventi sarebbe esposto per altri oggetti per registrare i gestori di eventi su.

Ciò consentirebbe a WorldScreen di creare la propria implementazione di gestori di eventi (osservatori) disaccoppiati dall'implementazione mondiale e quindi registrare questo gestore di eventi sul dispatcher di Worlds e attendere gli eventi innescati e reagire a loro (ad es. player is dead = > show "Sei morto")

    
risposta data 13.08.2017 - 17:46
fonte
-3

Il problema è che il tuo design non deve implementare il pattern MVC.
Oppure MVVM o MVP - chiamalo come preferisci ...

Ciò causa dipendenze circolari.

Can you explain how the MVC pattern would help in such case? Why is it better? – Winter

Aiuta a strutturare le dipendenze.

What does it requires? – Winter

Richiede una progettazione chiara di ogni livello

In this case, the "model" is a complex system with inner events and running on its own.
What would be my controller? – Winter

Questo è il punto. Il "sistema complesso con eventi interni e in esecuzione autonoma" appartiene al livello controller che lavora sul modello .

Il livello visualizza gestisce le interazioni con l'utente e / o altri sistemi, "visualizza" i dati dal modello e chiama i servizi nel controller per cambiare i dati nel modello .

How would that relate to a regular data bean? – Winter

I bean di dati fanno parte del livello del modello.

Not every problem has a key-in-hand pattern to blindly apply. – Winter

Il pattern MVC è qualcosa di molto semplice come il paradigma input - process - output . Non è necessario seguirlo (ciecamente), ma se lo fai, ti salva da molti problemi, specialmente dal tipo di "dipendenza circolare".

    
risposta data 13.08.2017 - 17:22
fonte

Leggi altre domande sui tag