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?