Questo cattivo design OOP è una simulazione che coinvolge le interfacce?

13

Sto progettando il mio piccolo programma OOP per simulare Vampiri, Lupi, Umani e Camion e sto cercando di implementare la mia comprensione limitata delle Interfacce.

( Sono ancora in astratto qui e non ho ancora implementato il codice, quindi è piuttosto una questione di progettazione OOP ... credo!)

Ho ragione a cercare "comportamento comune" tra queste classi e implementarle come interfacce ?

Ad esempio, Vampiri e Lupi mordono ... quindi dovrei avere un'interfaccia per mordere?

public class Vampire : Villain, IBite, IMove, IAttack

Allo stesso modo per i camion ...

public class Truck : Vehicle, IMove

E per gli umani ...

public class Man : Human, IMove, IDead

Il mio pensiero è qui? (Apprezzo il tuo aiuto)

    
posta user3396486 29.09.2016 - 19:49
fonte

3 risposte

33

In generale, vuoi avere interfacce per le caratteristiche comuni del tuo clas.

Sono semi-d'accordo con @Robert Harvey nei commenti, che ha detto che solitamente le interfacce rappresentano più caratteristiche astratte delle classi. Tuttavia, trovo che partendo da esempi più concreti un buon modo di iniziare a pensare astratto.

Mentre il tuo esempio è tecnicamente corretto (cioè sì, sia i vampiri che i lupi mordono, quindi puoi avere un'interfaccia per quello), c'è una questione di rilevanza. Ogni oggetto ha migliaia di caratteristiche (ad esempio gli animali possono avere la pelliccia, possono nuotare, possono arrampicarsi sugli alberi e così via). Farai un'interfaccia per tutti loro? Molto meno probabile.

Di solito vuoi interfacce per cose che hanno senso essere raggruppate in un'applicazione nel suo complesso. Ad esempio, se stai costruendo un gioco, puoi avere una matrice di oggetti IMove e aggiornare la loro posizione. Se non vuoi farlo, avere l'interfaccia di IMove è piuttosto inutile.

Il punto è, non sovra ingegnerizzare. Devi pensare a come utilizzerai quell'interfaccia e 2 classi con un metodo in comune non sono un buon motivo per creare un'interfaccia.

    
risposta data 29.09.2016 - 20:31
fonte
28

Sembra che tu stia creando una serie di interfacce con un singolo metodo . Questo va bene a prima vista, ma tieni presente che le interfacce non sono di proprietà delle classi che li implementano. Sono di proprietà dei clienti che li utilizzano. I clienti decidono se qualcosa deve essere qualcosa che può muoversi e attaccare.

Se ho una classe Combat con un metodo fight() , è probabile che quel metodo richiami sia move() che attack() sullo stesso oggetto. Ciò suggerisce strongmente la necessità di un'interfaccia ICombatant che fight() possa chiamare move() e attack() a. Questo è più pulito di fight() prendendo un oggetto IAttack e convertendolo in IMove per vedere se può anche muoversi.

Questo non significa che non puoi avere anche IMove IAttack interfacce. Spero solo che tu non li stia facendo senza un cliente che ne ha bisogno. Viceversa, se nessun cliente ha mai bisogno di creare un oggetto sia in movimento che in attacco, ICombatant non è necessario.

Questo semplice modo di guardare le interfacce è spesso perso perché alla gente piace seguire degli esempi. Le prime interfacce a cui siamo esposti sono nelle librerie. Sfortunatamente, le biblioteche non hanno idea di cosa siano i loro clienti. Quindi possono solo intuire le esigenze dei loro clienti. Non è il miglior esempio da seguire.

    
risposta data 29.09.2016 - 20:31
fonte
3

Considera se sarà comune avere collezioni di oggetti con diverse combinazioni di abilità e se il codice potrebbe voler eseguire un'azione su quegli elementi, all'interno di una raccolta, che lo supportano . Se è così, e se ci sarebbe un "comportamento predefinito" ragionevole per oggetti che non hanno un supporto utile per qualche azione, potrebbe essere utile avere interfacce implementate da una vasta gamma di classi, non solo quelle che possono comportarsi in modo utile.

Ad esempio, supponiamo che solo alcuni tipi di creature possano avere Woozles, e uno vuole che tali creature abbiano una proprietà NumerOfWoozles . Se una tale proprietà fosse in un'interfaccia che è stata implementata solo da creature che possono avere Woozles, il codice che voleva trovare il numero totale di Woozles detenuti da una collezione di creature di tipi misti dovrebbe dire qualcosa del tipo:

int total = 0;
foreach (object it in creatures)
{
   IWoozleCountable w = trycast(it, IWoozleCountable);
   if (w != null) total += w.WoozleCount;
}

Se, comunque, WoozleCount fosse un membro di Creature / ICreature, anche se pochi sottotipi avrebbero sovrascritto l'implementazione WoozleCount predefinita di Creature che restituisce sempre zero, il codice potrebbe essere semplificato in:

int total = 0;
foreach (ICreature it in creatures)
   total += it.WoozleCount;

Mentre alcune persone si irritano all'idea che ogni Creatura implementa una proprietà WoozleCount che è davvero utile solo per alcuni sottotipi, la proprietà sarebbe significativa per tutti i tipi, indipendentemente dal fatto che sarebbe utile con elementi noti per questi tipi, e considererei l'interfaccia "kitchen sink" come meno un odore di codice rispetto all'operatore trycast.

    
risposta data 30.09.2016 - 17:42
fonte

Leggi altre domande sui tag