Questo cattivo design OOP per una simulazione coinvolge interfacce?


13

Sto progettando il mio piccolo programma OOP per simulare vampiri, lupi, esseri umani e camion e sto cercando di implementare la mia comprensione limitata delle interfacce.

( Sto ancora astrattando qui e non ho ancora implementato il codice, quindi è piuttosto una questione di progettazione OOP ... penso!)

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

Ad esempio, Vampires and Wolves mordono ... quindi dovrei avere un'interfaccia morso?

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)


14
Gli animali, le verdure e i minerali raramente costituiscono buoni esempi per le implementazioni delle applicazioni. Implementazioni reali sono generalmente più astratta, come IEnumerable, IEquatablee così via
Robert Harvey

6
Hai una sola menzione di ciò che i tuoi oggetti stanno per fare nel tuo software ("morso"). Il software è normalmente progettato per fare qualcosa, basare un modello a oggetti solo sulle caratteristiche non porta da nessuna parte.
martedì

@tofro La mia intenzione era che IBite contenesse più metodi che implementassero comportamenti riguardanti (1) La riduzione del livello di "vita / energia" di un altro (2) L'aspetto o l'invocazione della grafica del "sangue" e (3) l'aggiornamento della statica di simulazione dati (come NoOfBites). Penso di poter apprezzare che un'interfaccia è utilizzata al meglio per implementare una serie di comportamenti metodologici.
user3396486

2
Le classi Human, Vampire e Vehicle non implementano già l'interfaccia IMove? Perché è necessario che le sottoclassi lo implementino in modo troppo esplicito?
Pierre Arlaud,

Tutte queste interfacce sono davvero necessarie? Per fortuna in Python non hai bisogno di nessuna di queste cose, ehich è stato un cambiamento davvero rinfrescante (la mia prima lingua era Object Pascal). Anche i metodi virtuali potrebbero essere una soluzione migliore in alcuni casi.
Ajasja,

Risposte:


33

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

Sono semi-d'accordo con @Robert Harvey nei commenti, che hanno affermato che di solito le interfacce rappresentano caratteristiche più astratte delle classi. Tuttavia, trovo a partire da esempi più concreti un buon modo per iniziare a pensare in 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 domanda di rilevanza. Ogni oggetto ha migliaia di caratteristiche (ad esempio gli animali possono avere la pelliccia, nuotare, arrampicarsi sugli alberi e così via). Farai un'interfaccia per tutti loro? Molto meno probabile.

Di solito si desidera che le interfacce per le cose sensate siano raggruppate in un'applicazione nel suo insieme. Ad esempio, se stai costruendo un gioco, puoi avere una matrice di oggetti IMove e aggiornarne la posizione. Se non vuoi farlo, avere l'interfaccia IMove è abbastanza inutile.

Il punto è che non esagerare con l'ingegnere. Devi pensare a come intendi utilizzare quell'interfaccia e 2 classi che hanno un metodo in comune non sono una ragione sufficiente per creare un'interfaccia.


1
Spero sicuramente che ogni oggetto non abbia migliaia di attributi.
gardenhead,

4
Attributi non come negli attributi oop ma attributi / caratteristiche grammaticali (cose come enumerabili, comparabili, ecc.): D. Cattiva scelta delle parole.
Paul92,

3
Vale la pena notare che le interfacce utili sono quelle che userete. Ad esempio, IBitenon è particolarmente utile, ma potresti volerlo in IAttackmodo da poter lavorare su tutte le cose che fanno attacchi, o IUpdatecosì puoi eseguire gli aggiornamenti per tutto, o IPhysicsEnabledcosì puoi applicare la fisica ad essi, ecc.
anaximander

1
Questa risposta solleva alcuni punti molto positivi. Il paragrafo finale lo riassume abbastanza bene; almeno quanto è possibile con il livello di dettaglio fornito.
Corse di leggerezza con Monica,

1
raggruppare i metodi comuni si adatta meglio alle classi astratte. Vengono realizzate interfacce per progettare contratti che devono rispettare coloro che la implementano, non raggruppando la stessa implementazione per alcuni oggetti.
Walfrat,

28

Sembra che tu stia creando un sacco di interfacce a metodo singolo . Ciò va bene, ma tieni presente che le interfacce non sono di proprietà delle classi che le 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 Combatclasse con un fight()metodo, è probabile che quel metodo abbia bisogno di chiamare entrambi move()e attack()sullo stesso oggetto. Ciò suggerisce fortemente la necessità di ICombatantun'interfaccia in fight()grado di chiamare move()e attack()passare. Questo è più pulito che fight()prendere un IAttackoggetto e lanciarlo IMoveper vedere se può anche muoversi.

Ciò non significa che non puoi avere anche IMove IAttackinterfacce. Spero solo che tu non li stia realizzando senza che alcuni clienti ne abbiano bisogno. Al contrario, se nessun client deve mai far muovere e attaccare un oggetto, alloraICombatant non è necessario.

Questo semplice modo di vedere le interfacce viene spesso perso perché alla gente piacciono i seguenti 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 indovinare le esigenze dei loro clienti. Non è il miglior esempio da seguire.


1
Dannazione, va bene. Il gioco sembra proprio un ottimo modo per usare e spiegare OOP.
JeffO,

6
@JeffO fino a quando non realizzerai un gioco abbastanza grande e ti renderai conto che OOP è un casino caldo e che staresti meglio con sistemi basati su componenti o progetti orientati ai dati.
Darkhogg,

"Le interfacce sono di proprietà dei clienti che le usano"
Tibos,


1
+1 per la differenza tra librerie e applicazioni, spesso (troppo?: /) Leggo tonnellate di cose che si adattano solo all'una e non all'altra.
Walfrat,

3

Considera se sarà comune avere raccolte di oggetti con diverse combinazioni di abilità e se il codice potrebbe voler eseguire un'azione su quegli oggetti, all'interno di una raccolta, che lo supportano . In tal caso, e se ci fosse un "comportamento predefinito" ragionevole per gli oggetti che non hanno un supporto utile per alcune azioni, può 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 pochi tipi di creatura possano avere Woozles e che si desideri che tali creature abbiano una NumerOfWoozlesproprietà. Se una tale proprietà fosse in un'interfaccia implementata solo da creature che possono avere Woozles, allora il codice che voleva trovare il numero totale di Woozles posseduto 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, tuttavia, WoozleCount fosse un membro di Creature / ICreature, anche se pochi sottotipi sovrascriverebbero l'implementazione WoozleCount predefinita di Creature che restituisce sempre zero, il codice potrebbe essere semplificato per:

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

Mentre alcune persone potrebbero lamentarsi dell'idea che ogni Creatura implementi una proprietà WoozleCount che sia davvero utile solo per alcuni sottotipi, la proprietà sarebbe significativa per tutti i tipi, indipendentemente dal fatto che sarebbe utile con oggetti noti per essere di quei tipi, e considererei l'interfaccia del "lavello della cucina" come un odore di codice inferiore rispetto all'operatore trycast.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.