Ereditarietà rispetto a Iniezione con modello Template-ish e contributori pianificati

3

Sto cercando di sviluppare un'applicazione OSGi per gestire il mio motore di gioco. Questo motore di gioco utilizzerà il modello di sistema Entity-Component. Parte di questo modello è l'uso di GameSystems .

Inizialmente avevo pianificato di esporre l'interfaccia GameSystem come bundle; in questo modo altri contributori possono creare i propri sistemi e aggiungerli al motore di gioco tramite i bundle OSGi. Quando ho scritto l'interfaccia iniziale sembrava

public interface GameSystem {

    boolean isInterestedIn(GameEntity entity);

    void update(GameEntity entity);
}

Quindi ho scritto un'implementazione astratta di questo per aggiungere un controllo di precondizione (restituendo false se l'entità è null).

public abstract class AbstractGameSystem implements GameSystem {

    public boolean isInterestedIn(GameEntity entity) {
        if (entity == null) {
            return false;
        }
        // TODO
    }
}

A questo punto mi sono reso conto che avevo bisogno di una sorta di meccanismo per consentire alla sottoclasse di aggiungere le proprie condizioni, senza essere costretto a chiamare super.isInterestedIn(entity) . Ho creato un metodo astratto protetto che le sottoclassi potrebbero implementare.

public abstract class AbstractGameSystem implements GameSystem {

    protected abstract boolean checkEntity(GameEntity entity);

    public final boolean isInterestedIn(GameEntity entity) {
        if (entity == null) {
            return false;
        }
        return checkEntity(entity);
    }
}

A questo punto faccio un passo indietro e mi chiedo se forse dovrei, invece di dipendere dall'ereditarietà, passare un predicato e un consumatore come parametri del costruttore, e non preoccuparti affatto di una classe astratta.

public final class InjectedGameSystem implements GameSystem {

    private final Predicate<GameEntity> predicate;
    private final Consumer<GameEntity> consumer;

    public InjectedGameSystem(Predicate<GameEntity> predicate, Consumer<GameEntity> consumer) {
        this.predicate = Objects.requireNonNull(predicate);
        this.consumer = Objects.requireNonNull(consumer);
    }

    public boolean isInterestedIn(GameEntity entity) {
        return predicate.test(entity);
    }

    public void update(GameEntity entity) {
        consumer.accept(entity);
    }
}

L'esecuzione prevista di questo codice sarebbe nel motore di gioco, qualcosa come

public void run() {
    while (shouldRun()) {
        for (GameEntity entity : entities) {
            for (GameSystem system : systems) {
                if (system.isInterestedIn(entity)) {
                    system.update(entity);
                }
            }
        }
    }
}

Questo ha rivelato una domanda interessante nella mia testa. Se mi aspetto sempre che un pezzo di codice venga eseguito in un certo modo (chiama isInterestedIn seguito da update se restituisce true) e se fornisco un modo per delegare tutte le funzionalità ad altri oggetti (il predicato e il consumatore) , c'è qualche motivo per fornire l'interfaccia, invece della sola classe di delega?

    
posta Zymus 29.12.2017 - 01:36
fonte

1 risposta

0

Una classe come InjectedGameSystem che semplicemente delega tutti i suoi metodi essenzialmente è un'interfaccia. La differenza è che è possibile assemblare l'implementazione in fase di esecuzione, fornendo le funzioni di delegato appropriate.

Se per una classe di questo tipo è logico implementare un'interfaccia dipende dall'utilizzo. Posso pensare a tre potenziali ragioni per avere un'interfaccia separata:

  • Devi rispettare un'interfaccia esistente, perché è richiesta da un consumatore della classe.

    Ad esempio, applico spesso il tuo approccio alla creazione dinamica di oggetti che implementano l'interfaccia ICommand in .NET. Chiaramente, questo non è il tuo caso.

  • Hai implementazioni che agiscono come GameSystem , ma devi anche fare altre cose e possibilmente ereditare da altre classi.

    Ancora una volta, da quello che hai scritto, non sembra che tu ne abbia bisogno. Inoltre, puoi sempre aggirare il problema, facendo in modo che la classe fornisca un metodo InjectedGameSystem AsGameSystem() .

  • Si desidera nascondere il fatto che le chiamate al metodo sono delegate, poiché si tratta di un dettaglio di implementazione. Questo è più rilevante se la creazione e il consumo degli oggetti avvengono in moduli diversi.

Personalmente, probabilmente terrei l'interfaccia, ma penso che sarebbe difficile stabilire che un approccio sia significativamente migliore o peggiore dell'altro.

    
risposta data 04.01.2018 - 11:52
fonte

Leggi altre domande sui tag