2015-06-17 67 views
8

Lo scenario. Sto scrivendo il codice relativo al gioco. In quel gioco uno Player (è anche una classe) ha un elenco di Item. Esistono altri tipi di elementi che ereditano da Item, ad esempio ContainerItem, DurableItem o WeaponItem.È buona norma usare spesso instanceof?

Ovviamente è molto conveniente per me avere solo List<Item>. Ma quando ottengo gli elementi dei giocatori, l'unico modo per me di distinguere tra che tipo di elemento è utilizzando la parola chiave instanceof. Sono sicuro di aver letto che relying su di esso è una cattiva pratica.

È corretto utilizzarlo in questo caso? O dovrei ripensare a tutta la mia struttura?

+0

È possibile evitare la necessità di utilizzare instanceof se articolo definisce un metodo astratto, costringendo ogni sottoclasse di eseguire il proprio. – NickJ

+0

Probabilmente ripensa la tua struttura. Lo schema che stai descrivendo è davvero un anti-modello. – MadConan

+0

Sì, in generale l'uso di 'instanceof'' è un sintomo di un design cattivo/scarso. Il trucco è usare il doppio dispatch o modello di visitatore. Guarda sul web. –

risposta

10

Diciamo che sto scrivendo un certo codice di inventario:

public void showInventory(List<Item> items) { 
    for (Item item : items) { 
     if (item instanceof ContainerItem) { 
      // container display logic here 
     } 
     else if (item instanceof WeaponItem) { 
      // weapon display logic here 
     } 
     // etc etc 
    } 
} 

che compilerà e funzionano bene. Ma manca un'idea chiave del design orientato agli oggetti: È possibile definire le classi genitore per fare cose utili generali e fare in modo che le classi figlie inseriscano dettagli specifici importanti.

approccio alternativo sopra:

abstract class Item { 
    // insert methods that act exactly the same for all items here 

    // now define one that subclasses must fill in themselves 
    public abstract void show() 
} 
class ContainerItem extends Item { 
    @Override public void show() { 
     // container display logic here instead 
    } 
} 
class WeaponItem extends Item { 
    @Override public void show() { 
     // weapon display logic here instead 
    } 
} 

Ora abbiamo un posto per guardare, il metodo show(), in tutte le nostre classi figlie per la logica di visualizzazione dell'inventario. Come lo accediamo? Facile!

public void showInventory(List<Item> items) { 
    for (Item item : items) { 
     item.show(); 
    } 
} 

Stiamo mantenendo tutta la logica specifica per voce dentro specifiche sottoclassi Item. Ciò facilita la manutenzione e l'estensione del codice base. Riduce la deformazione cognitiva del ciclo lungo per ogni ciclo nel primo esempio di codice. Inoltre, è pronto per essere riutilizzato in luoghi che non hai ancora progettato, show().

+0

Questo è il modo corretto di farlo, ma a volte non è sufficiente. Prendiamo ad esempio la classe 'Record' - abbiamo 2 tipi di record -' File' e 'Directory'. Le cose comuni possono essere astratte in record, ma diciamo che 'File' ha un metodo' download() '. Quindi da un 'Set ' non è possibile scaricare i file a meno che non si sappia che si tratta effettivamente di file. Quindi, se cadi in quel caso, il polimorfismo non è sufficiente, ma nella maggior parte dei casi è la soluzione migliore, quindi +1 per la buona risposta –

+0

@SvetlinZarev Puoi anche astrarre questo - implementando metodi che hanno senso a lanciare UnsupportedOperationException (ad es. java.util.Iterator.remove), fornendo un metodo di restituzione o aggiunta Null-Object/Default per interrogare se un metodo è applicabile. È tutta una questione di sforzo, non una principale impossibilità. – Durandal

+1

Lo considero un cattivo progetto per aggiungere metodi a un'interfaccia, solo per lanciare "UnsupportedOperationException". Anche lo scopo del modello di oggetto nullo è completamente diverso. Sì, potrebbe portare a termine il lavoro, ma sarà più come un hack, che una soluzione areale. Prendiamo ad esempio il mio esempio "Record". Perché dovrei inquinare l'interfaccia 'Record' con metodi che non sono applicabili a ciascun record? In questo modo sto solo rendendo l'API più difficile da utilizzare, ovvero i client dovranno gestire eccezioni stupide o valori di ritorno inutili. –

2

Si dovrebbe ripensare la struttura, instanceof in codice non meta è quasi sempre un segno per un anti-pattern. Prova a definire il comportamento che tutti i Item hanno in comune (come avere un'immagine, una descrizione e qualcosa che accade quando si fa clic su di essi) nell'interfaccia Item, utilizzando la parola chiave abstract se appropriato e quindi utilizzare polymorphism per implementare le specifiche.

2

IMHO utilizzando instanceof è un odore di codice. In poche parole: rende il codice procedurale, non orientato agli oggetti. Il modo OO per farlo è utilizzare lo visitor pattern.

enter image description here

Il modello visitatore consente inoltre di creare facilmente decorator s e chain of responsibility su di esso, ottenendo così la separazione degli interessi, che si traduce in più brevi, più pulito e più facile da leggere e il codice di prova.

Inoltre, lo è davvero necessario conoscere la classe esatta? Non puoi approfittare del polimorfismo? Dopotutto lo Axe è un Weapon proprio come lo è Sword.

+7

IMO, il modello di visitatore è un grande modo di mantenere il tuo lavoro sicuro - nessun altro può capire il tuo codice! – NickJ

+0

Non sono d'accordo, lo schema del viditore consente di creare facilmente 'decoratore' e 'catena di responsabilità' su di esso per ottenere la' separazione delle preoccupazioni ', rendendo quindi il codice molto più semplice e facile da leggere. Applica anche la sicurezza del tipo - non puoi aggiungere una nuova sottoclasse di T senza inciampare su un errore di compilazione, mentre eith 'instanceoff' scoprirai il problema fino a quando durante il runtime –

+0

Il mio commento non era serio, ma ho provato ripetutamente a capisco il motivo del visitatore, e non ci sono mai riuscito. Ho dovuto mantenere il codice con lo schema del visitatore e l'ho trovato frustrante. – NickJ

3

È consigliabile ripensare forse e provare a utilizzare il polimorfismo per implementare l'idea List<Item>.

Ecco alcuni riferimenti per il vostro problema che probabilmente può aiutare:


(Riferimenti da Is instanceof considered bad practice? If so, under what circumstances is instanceof still preferable?)

0

Va bene se è facile da capire.

Lo spostamento delle logiche di ramificazione da cui appartengono naturalmente a sottoclassi non è necessariamente una buona idea. Può creare la dipendenza sbagliata; può causare classi gonfiate, e può essere difficile da navigare e capire.

Alla fine, è tutto su come organizzare fisicamente il nostro codice con più dimensioni di preoccupazioni in uno spazio unidimensionale. Non è un problema banale, è soggettivo e non esiste una panacea.

In particolare, il nostro settore ha ereditato un sacco di regole empiriche che sono state fatte nel secolo scorso sulla base di vincoli tecnici ed economici di quell'epoca. Ad esempio, gli utensili erano molto limitati; i programmatori erano molto costosi; le applicazioni si sono evolute lentamente.

Alcune di queste regole potrebbero non essere più applicabili oggi.

0

Non penso che l'instanceof sia un problema per i programmatori che sanno cosa stanno facendo e lo usano per evitare di dover scrivere codice più complicato per aggirarlo. C'è un uso per tutto, e anche un uso improprio.

Detto ciò, la descrizione fornita non richiede l'istanza. Esistono vari modi per implementarlo senza instanceof e (la cosa più importante) la soluzione alternativa deve essere migliore rispetto all'utilizzo di instanceof. Non andare con una soluzione non-instanceof per evitare l'instanceof perché hai sentito che è male.

Penso che per il proprio scenario una soluzione non-instanceof abbia vantaggi nel rendere la soluzione più facilmente estendibile.

for (Item i: items) { 
    if (ItemTypes.WEAPON_ITEM.equals(i.getType)) { 
     processWeaponType(i); 
    } 

} 

o

for (Item i: items) { 
    if (WeaponItem.class.equals(i.getType)) { 
     processWeaponType(i); 
    } 

} 
+0

bene, ma la tua soluzione non è più leggibile di 'instanceof'. Per quanto riguarda le prestazioni, 'instanceof' è molto veloce; è decisamente più veloce rispetto al confronto con il nostro campo 'tipo'; potrebbe anche essere più veloce del confronto di 'Class' con' == '. – ZhongYu

+0

Forse, ma è veloce il risultato desiderato o la progettazione e l'architettura del software rispetto a questa domanda? Voglio dire, possiamo sbarazzarci delle classi tutte insieme e mettere tutto in un unico lungo metodo se pensi davvero che la differenza di prestazioni valga la pena di essere presa in considerazione per questa domanda. Essendo che il compilatore JIT riorganizzerà comunque il codice, è difficile dire quale sia più veloce. –

+0

Questo non è migliore di instanceof, è un * camuffamento * per esempio di. Nessuno dei due suggerimenti risolve il problema di dover controllare/aggiornare tutto il codice client quando si aggiunge un nuovo tipo di bambino. – Durandal