È una cattiva pratica controllare i tipi di oggetto con una variabile membro identificativa?

3

Preambolo: Sto facendo un semplice gioco da tavolo in C ++, in cui i personaggi di IA si muovono attorno ai quadrati sulla scacchiera. Esistono diversi tipi di quadrati, ognuno ereditato da una classe astratta, ciascuno con effetti diversi sul carattere AI. Ad esempio, alcuni sono negozi in cui il personaggio può acquistare oggetti, altri sono luoghi di lavoro in cui i personaggi ricevono denaro, ecc.

Allo stesso modo, ci sono diversi personaggi AI (ereditati anche da una classe astratta), che fanno diversi tipi di decisioni. Alcuni lavorano più degli altri, altri mangiano più degli altri, ecc.

Il problema è che il personaggio AI deve conoscere il tipo di quadrato per prendere una decisione su cosa fare. Al momento la verifica del tipo utilizzando una stringa di tipo come variabile membro è la soluzione più semplice, ma risulta orribilmente inelegante e non molto utile quando si tratta di aggiungere nuovi tipi di quadrati e caratteri AI.

Domanda: È una cattiva pratica controllare il tipo di un oggetto chiedendogli un identificatore inserito nella sua classe per questo scopo? È una cattiva pratica utilizzare altri metodi di identificazione del tipo di runtime come dynamic_cast e typeinfo per controllare il flusso di esecuzione?

Esempio: Sotto la classe Square (accessibile tramite currentSquare () ha una funzione membro getType () che restituisce una stringa con il nome del tipo:

//Normal AI character
void decide() {
    if(currentSquare()->getType()=="HOME") {
        watchTV();
        sleep();
    } 
    if(currentSquare()->getType()=="WORKPLACE") {
        work();
    } 
}

//Party animal AI
void decide() {
    if(currentSquare()->getType()=="HOME") {
        moveTo(getGameBoard()->closestBar());
    } 
    if(currentSquare()->getType()=="WORKPLACE") {
        sleep();
    } 
}
    
posta Atuos 14.10.2013 - 21:47
fonte

3 risposte

11

Come ha sottolineato Amon, questa è una buona applicazione per il modello di visitatore . Usandolo, le tue classi AI finiranno per sembrare qualcosa del genere:

void decide(HomeSquare square);
void decide(WorkSquare square);
void decide(ShopSquare square);

E i tuoi quadrati hanno una funzione accept che assomiglia a:

void accept(AI ai)
{
    ai.decide(this);
}

Ciò consente di utilizzare l'ereditarietà per creare impostazioni predefinite appropriate o imporre a ogni AI di implementare determinate decisioni. Puoi creare un'IA che si comporta esattamente come un'altra, tranne che a casa, ad esempio, senza copiare un mucchio di codice. Il compilatore ti indicherà dove ti dimentichi qualcosa e non devi eseguire il cast dappertutto.

    
risposta data 14.10.2013 - 23:37
fonte
3

Vorrei sottolineare la fragilità dell'uso di stringhe per l'identificazione di classi di oggetti. In qualche modo stai lavorando attorno al tipo di sicurezza e se qualcosa va storto potresti ricevere strani errori in fase di esecuzione. Se hai un bel po 'di classi, vuoi controllare, può diventare un problema aggiornare tutte le parti del codice se introduci una nuova classe o ne elimini un'altra. Inoltre complica o addirittura impedisce l'analisi statica riducendo così la probabilità di trovare automaticamente bug (come il codice morto).

Confrontalo con il modello di visitatore che altri già hanno indicato. Otterrai la sicurezza del tipo e (se qualcosa va storto) compili errori di tempo. Inoltre, il modello di visitatore offre maggiore flessibilità e non si otterranno blocchi if-else o blocchi di switch lunghi in cascata, migliorando così la leggibilità e la manutenibilità. Gli strumenti di analisi statica funzioneranno come previsto.

Quindi direi che l'uso delle variabili membro per l'identificazione è al massimo ottimale.

Full disclosure: sono principalmente uno sviluppatore Java, ma penso che in questo caso le differenze tra java e c ++ non siano un problema. Se ho torto qualcuno, per favore correggimi. ;)

    
risposta data 15.10.2013 - 00:59
fonte
2

Preferisco usare enum piuttosto che stringhe per situazioni come questa, e il codice "check type" dovrebbe essere scritto per escludere i casi non specificati.

switch (currentSquare()->getType())
{ default: BUG("case not expected");
  case workplace: ...
    break;
  case home: ..
    break;
}
    
risposta data 14.10.2013 - 21:54
fonte

Leggi altre domande sui tag