2015-11-22 25 views
21

Nella mia applicazione ho una matrice 2d di entità per rappresentare una griglia. Ogni posizione nella griglia può essere vuota o occupata da un'entità (in questo caso è solo una persona o un muro). In questo momento io uso instanceof per verificare se un'entità è una persona o un muro.Istanza di Avoding in Java

Stavo pensando di assegnare a ciascuna entità un metodo che restituisce un enum indicando il loro tipo quindi, ad esempio, un'entità muro restituirà EntityType.WALL. Mi chiedevo se questa è l'idea migliore per rimuovere l'uso di instanceof o è instanceof adatto in questo scenario?

+2

Per me, la soluzione 'enum' non sembra molto migliore dal punto di vista della progettazione software rispetto alla soluzione' instanceof'. Probabilmente si può risolvere questo molto più elegantemente usando il polimorfismo del sottotipo (cioè creare sottoclassi e sovrascrivere alcuni metodi con comportamenti specifici di Person/Wall/Empty). L'altra opzione è quella di utilizzare l'invio dinamico, che viene ottenuto in modo limitato (doppia spedizione) attraverso il modello di visitatore. Penso che ora ci siano le risposte qui sotto che affrontano ciascuno di questi approcci. – DaoWen

+1

Nota: c'è un errore di battitura nel titolo. Ma non puoi sistemarlo perché c'è già una domanda chiamata "Evitare l'instanceof in Java". Ti suggerisco di riformulare il titolo per essere più specifico. – Tunaki

+0

Possibile duplicato di [Evitare instanceof in Java] (http://stackoverflow.com/questions/2790144/avoiding-instanceof-in-java) – Thomas

risposta

0

avrei lasciato persona e la parete ereditano da una superclasse astratta (ad es Tile), che ha un metodo getType() che restituisce un enum o int e implementare questo metodo a Wall e persona di restituire il appropriata

+0

Questo va bene? classe astratta pubblica Entity { \t public abstract EntityType getEntityType(); } public class parete estende Entity { \t tipo EntityType finale private static = EntityType.PEDESTRIAN; \t @Override \t pubblico EntityType getEntityType() Tipo di \t { \t \t di ritorno; –

+0

Invece di utilizzare l'ereditarietà, sarebbe meglio utilizzare un'interfaccia (ad esempio 'Renderable') qui, perché né un Muro né una Persona è una Piastrella. –

+0

Stupid 5 minute rule sorry ~ Esiste un modo per forzare le classi che implementano Entity per avere un campo EntityType e che non può essere nullo? Se aggiungo il campo alla classe astratta, una classe potrebbe estendere Entity ma non impostare un EntityType se questo ha senso. –

-1

Come accennato in this other question i moderni compilatori Java sono molto efficienti in operazioni come l'istanza di. Dovresti stare bene usandolo.

In effetti, una delle altre risposte fornite testati instanceof e stringhe confronti, e instanceof era significativamente più veloce. Ti consiglio di continuare ad usarlo.

+5

Il problema con 'instanceof' non è un problema di efficienza, ma piuttosto un problema di progettazione software. L'uso di 'instanceof' è considerato un" odore di codice "che indica che dovresti davvero usare un'astrazione software migliore per risolvere il tuo problema (nella mia esperienza, di solito sia polimorfismo di sottoclasse o dispatch dinamico). – DaoWen

+0

@DaoDao hai ragione. D'altra parte, non penso che questa risposta meritasse di fare downvoting. Un downvote significa "questa risposta non è utile". –

+1

https://en.wikipedia.org/wiki/Overengineering - 'instanceof' è un odore di design, ma allora? YAGNI secondo me prevale. L'applicazione fa ciò che ti serve per fare? si - allora hai finito; no - cosa c'è che non va, magari aggiustando ciò che comporterebbe la fissazione dell'odore del codice, forse no. – emory

42

Utilizzare Tell, Don't Ask: anziché chiedere agli oggetti cosa sono e quindi reagire, dire all'oggetto cosa fare e quindi i muri o le persone decidono come fanno ciò che devono fare.

Ad esempio:

Invece di avere qualcosa di simile:

public class Wall { 
    // ... 
} 

public class Person { 
    // ... 
} 

// later 
public class moveTo(Position pos) { 
    Object whatIsThere = pos.whatIsThere(); 
    if (whatIsThere instanceof Wall) { 
     System.err.println("You cannot move into a wall"); 
    } 
    else if (whatIsThere instanceof Person) { 
     System.err.println("You bump into " + person.getName()); 
    } 
    // many more else branches... 
} 

fare qualcosa di simile:

public interface DungeonFeature { 
    void moveInto(); 
} 

public class Wall implements DungeonFeature { 
    @Override 
    public void moveInto() { 
     System.err.println("You bump into a wall"); 
    } 

    // ... 
} 

public class Person implements DungeonFeature { 
    private String name; 

    @Override 
    public void moveInto() { 
     System.err.println("You bump into " + name); 
    } 

    // ... 
} 

// and later 
public void moveTo(Position pos) { 
    DungeonFeature df = currentPosition(); 
    df.moveTo(pos); 
} 

Questo ha alcuni vantaggi.

Per prima cosa, non è necessario regolare un albero gigante se non altro ogni volta che si aggiunge una nuova funzione di dungeon.

In secondo luogo, il codice nelle funzionalità di Dungeon è autonomo, la logica è tutto nell'oggetto indicato. Puoi facilmente testarlo e spostarlo.

+1

Mi piace questa risposta, ma penso che sia necessario espanderlo un po '. Forse puoi aggiungere un esempio davvero semplice per illustrare il tuo punto. – DaoWen

+2

@DaoDao ok, fatto – Robert

8

La soluzione teorica alla rimozione di instanceof in a refined way è l'utilizzo di Visitor Pattern. Come funziona è che l'oggetto che ha bisogno di sapere se l'altro elemento è un muro o una persona chiama quell'oggetto con se stesso come parametro, e quel particolare oggetto richiama fornendo così informazioni sul suo tipo.

Esempio,

public class Person { 
    void magic() { 
     if(grid.getAdjacent() instanceof Person) { 
      Person otherPerson = (Person)grid.getAdjacent(); 
      doSomethingWith(otherPerson); 
     } else if(grid.getAdjacent() instanceof Wall) { 
      Wall wall = (Wall)grid.getAdjacent(); 
      doOtherThingWith(wall); 
     } 
    } 
} 

può diventare risposta

public class Person extends Entity { 
    void magic() { 
     grid.getAdjacent().visit(this); 
    } 

    void onVisit(Wall wall) { 
     doOtherThingWith(wall); 
    } 

    void onVisit(Person person) { 
     doSomethingWith(person); 
    } 

    public void visit(Person person) { 
     person.onVisit(this); 
    } 
} 

public class Wall extends Entity { 
    public void visit(Person person) { 
     person.onVisit(this); 
    } 
} 
+0

E il caso 'Empty'? (So ​​che probabilmente ti sembra ovvio che puoi aggiungere un caso vuoto, ma se l'OP non ha mai visto prima il Pattern visitatore, potrebbe non essere così ovvio per loro.) – DaoWen

-1

di Ligi è proprio sul denaro. (Chiunque abbia svalutato, mi chiedo cosa stessero pensando.) In alternativa, considerare questo:

abstract class Tile 
{ 
    public final EntityType type; 

    protected Tile(EntityType type) 
    { 
     this.type = type; 
    } 
} 

abstract class Pedestrian extends Tile 
{ 
    public Pedestrian() 
    { 
     super(EntityType.PEDESTRIAN); 
    } 
} 

abstract class Wall extends Tile 
{ 
    public Wall() 
    { 
     super(EntityType.WALL); 
    } 
} 

La logica dietro questo è che il "tipo" dell'entità è una caratteristica permanente della entità, per cui è adeguata indicata nel costruttore e da realizzare in un campo membro final. Se viene restituito da un metodo virtuale (metodo non finale in java parlance), i discendenti sarebbero liberi di restituire un valore in un punto nel tempo e un altro valore in un altro punto nel tempo, il che significherebbe il caos.

Oh, e se davvero non riesci a sopportare il membro finale pubblico, vai avanti e aggiungi un getter per questo, ma il mio consiglio sarebbe che non importa ai puristi, i membri finali pubblici senza getter sono perfettamente bene.

+2

Non stai aggiungendo alcun valore avendo un enum che fornisce la stessa informazione instanceOf fornisce comunque. Il modo per risolvere questo è il polimorfismo e il corretto uso dei principi OO. –

1

Se segui le altre risposte qui e implementi un modello di visitatore o usi un enum, non commetterai un errore.

Tuttavia, potrebbe anche aiutare a pensare a cosa esattamente si vuole fare con quella logica di commutazione (sia esso instanceof o visitatori), perché a volte c'è un modo più semplice per farlo.

Ad esempio, se tutto ciò che si vuole fare è controllare se un'entità occupa una griglia in un modo di blocco, è sufficiente aggiungere un metodo boolean isSolid() a ciascuna entità tramite l'interfaccia. È possibile utilizzare questo con metodi predefiniti per la bellezza in più:

public interface GridPhysics { 
    default boolean isSolid() { 
     return true; 
    } 

    // other grid physics stuff 
} 

public class Wall implements GridPhysics { 
    // nothing to do here, it uses the default 
} 

// in your game logic 
public boolean canMoveTo(GridPhysics gridCell) { 
    return !gridCell.isSolid() && otherChecks(); 
} 

si potrebbe anche voler dare un'occhiata a sistemi componenti entità (ad esempio Artemis), che sostanzialmente prendono questa idea di "composizione per l'eredità" alle estreme conseguenze.

0

Le risposte sono molto buone qui nulla da dire su questo, ma se fossi in tale situazione e se è permesso di quanto sarei stato andato per un array int 2d con possibile valore 0 (per vuoto per assegnazione di default) e 1,2 per persona o muro.