Dipendenza di classe circolare


12

È male progettare avere 2 classi che hanno bisogno l'una dell'altra?

Sto scrivendo un piccolo gioco in cui ho una GameEngineclasse che ha alcuni GameStateoggetti. Per accedere a diversi metodi di rendering, questi GameStateoggetti devono anche conoscere la GameEngineclasse, quindi è una dipendenza circolare.

Chiameresti questo cattivo design? Lo sto solo chiedendo, perché non sono del tutto sicuro e in questo momento sono ancora in grado di riformattare queste cose.


2
Sarei molto sorpreso se la risposta fosse sì.
doppelgreener,

Poiché ci sono due domande, che sono logicamente disgiunte .. la risposta è sì a una di esse. Perché vuoi progettare uno stato che effettivamente fa qualcosa? dovresti avere un manager / controller per controllare lo stato ed eseguire l'azione. È un po 'confuso lasciare che un tipo di oggetto di tipo assuma le responsabilità di qualcos'altro. Se vuoi farlo, C ++ è tuo amico .
teodron,

Le mie lezioni di GameState sono cose come l'introduzione, il gioco reale, un menu e così via. GameEngine memorizza questi stati in una pila, quindi sono in grado di mettere in pausa uno stato e aprire un menu o riprodurre un filmato, ... Le classi GameState devono conoscere GameEngine, perché il motore crea la finestra e ha il contesto di rendering .
shad0w,

5
Ricorda, tutte queste regole mirano al digiuno. Veloce da eseguire, veloce da creare, veloce da mantenere. A volte queste sfaccettature sono in contrasto tra loro ed è necessario eseguire un'analisi costi-benefici per decidere come procedere. Se ci pensi così tanto e fai una telefonata, stai ancora facendo meglio del 90% degli sviluppatori. Non c'è nessun mostro nascosto che ti ucciderà se lo fai in modo sbagliato, è più un operatore nascosto di caselli.
DampeS8N,

Risposte:


0

Non è un cattivo design per natura, ma può facilmente sfuggire al controllo. In effetti stai usando quel disegno nei tuoi codici di tutti i giorni. Ad esempio, vector sa che è il primo iteratore e gli iteratori hanno un puntatore al loro contenitore.

Ora nel tuo caso, è molto meglio avere due classi separate per GameEnigne e GameState. Dal momento che sostanzialmente questi due stanno facendo cose diverse, e in seguito potresti definire molte classi che ereditano GameState (come un GameState per ogni scena del tuo gioco). E nessuno può negare la necessità di avere accesso gli uni agli altri. Fondamentalmente GameEngine sta eseguendo gamestates, quindi dovrebbe avere un puntatore su di essi. GameState utilizza le risorse definite in GameEngine (come rendering, manager della fisica, ecc.).

Non puoi e non dovresti unire queste due classi l'una con l'altra, poiché stanno facendo cose diverse per natura e la combinazione si tradurrà in una classe molto grande che a nessuno piace.

Finora sappiamo che abbiamo bisogno di dipendenza circolare nella progettazione. Esistono diversi modi per crearlo in modo sicuro:

  1. Come hai suggerito, possiamo mettere un puntatore di ciascuno di essi in un altro, è la soluzione più semplice ma potrebbe facilmente sfuggire al controllo.
  2. Puoi anche usare il design singleton / multiton, con questo metodo dovresti definire la tua classe GameEngine come singleton e ciascuno di questi GameState come multiton. Sebbene molti sviluppatori considerino sia Singleton che Multiton come AntiPattern, preferisco questo tipo di design.
  3. Puoi usare le variabili globali, ehi sono praticamente la stessa cosa di Singleton / multiton ma hanno una leggera differenza nel fatto che non limitano il programmatore no a creare istanze a piacimento.

Per concludere la sua risposta, potresti usare uno di questi tre metodi, ma devi stare attento a non usarli troppo poiché, come ho detto, tutti quei progetti sono davvero pericolosi e potrebbero facilmente portare a un codice non mantenibile.


6

È male progettare avere 2 classi che hanno bisogno l'una dell'altra?

È un po 'un odore di codice , ma si può partire con esso. Se questo è il modo più semplice e veloce per avviare il gioco, provalo. Ma tienilo a mente perché ci sono buone possibilità che a un certo punto dovrai rifattorizzarlo.

La cosa con C ++ è che le dipendenze circolari non si compileranno così facilmente , quindi potrebbe essere una buona idea sbarazzarsene invece di perdere tempo a sistemare la compilazione.

Vedi questa domanda su SO per qualche altra opinione.

Definiresti [il mio design] cattivo design?

No, è ancora meglio che mettere tutto in una classe.

Non è così eccezionale, ma in realtà è abbastanza vicino alla maggior parte delle implementazioni che ho visto. Di solito, avresti una classe manager per gli stati di gioco ( attenzione! ) E una classe renderer, ed è abbastanza comune che siano singoli. Quindi la dipendenza circolare è "nascosta", ma potenzialmente è lì.

Inoltre, come ti è stato detto nei commenti, è un po 'strano che le classi di stato del gioco eseguano una sorta di rendering. Dovrebbero semplicemente contenere informazioni sullo stato e il rendering dovrebbe essere gestito da un renderer o da alcuni componenti grafici degli oggetti di gioco stessi.

Ora potrebbe esserci il massimo del design. Sono curioso di vedere se altre risposte portano una bella idea. Tuttavia, sei probabilmente quello che può trovare il miglior design per il tuo gioco.


Perché dovrebbe essere un odore di codice? La maggior parte degli oggetti con relazione genitore-figlio deve vedersi.
Kikaimaru,

4
Perché è il modo più semplice per non definire quale classe è responsabile di cosa. Finisce facilmente in due classi che sono così profondamente accoppiate che l'aggiunta di nuovo codice può essere fatta in una di esse, quindi non sono più concettualmente separate. È anche una porta aperta per il codice spaghetti: la classe A chiama la classe B per questo, ma B ha bisogno di quelle informazioni da A, ecc. Quindi no, non direi che un oggetto figlio dovrebbe sapere del suo genitore. Se possibile, è meglio se non lo fa.
Laurent Couvidou,

È un errore di pendenza scivoloso. Le classi statiche possono anche portare a cose cattive, ma le classi util non sono odore di codice. E dubito davvero che ci sia un modo "buono" per fare cose come Scene con SceneNodes e SceneNode ha riferimenti a Scene, quindi non puoi aggiungerlo a due scene diverse ... E dove vorresti fermarti? A è richiesto B, B richiede C e C richiede A un odore di codice?
Kikaimaru,

In effetti, questo è proprio come le classi statiche. Maneggia con cura, usalo solo se devi. Ora parlo per esperienza e non sono l'unico che la pensa in questo modo , ma, in realtà, questa è una questione di opinione. La mia opinione è che nel contesto dato dal PO, questo è davvero maleodorante.
Laurent Couvidou,

Non c'è menzione di quel caso in quell'articolo di Wikipedia. E non puoi dire che sia un caso di "intimità inappropriata" fino a quando non avrai visto quelle classi.
Kikaimaru,

6

Spesso è considerato un cattivo progetto dover avere 2 classi che si riferiscono direttamente l'una all'altra, sì. In termini pratici può essere più difficile seguire il flusso di controllo attraverso il codice, la proprietà degli oggetti e la loro durata possono essere complicate, significa che nessuna classe è riutilizzabile senza l'altra, potrebbe significare che il flusso di controllo dovrebbe davvero vivere al di fuori di entrambi di queste classi in una terza classe "mediatore" e così via.

Tuttavia, è molto comune che 2 oggetti si riferiscano l'un l'altro e la differenza qui è che di solito la relazione in una direzione è più astratta. Ad esempio, in un sistema Model-View-Controller, l'oggetto View può contenere un riferimento all'oggetto Model e ne saprà tutto, potendo accedere a tutti i suoi metodi e proprietà in modo che la View possa popolarsi con dati pertinenti . L'oggetto Modello può contenere un riferimento alla vista in modo da poter effettuare l'aggiornamento della vista quando i suoi dati sono cambiati. Ma piuttosto che il modello con un riferimento alla vista - che renderebbe il modello dipendente dalla vista - di solito la vista implementa un'interfaccia osservabile, spesso con solo 1Update()e il modello contiene un riferimento a un oggetto osservabile, che può essere una vista. Quando il Modello cambia, chiama Update()tutti i suoi Osservabili e la Vista implementa Update()richiamando il Modello e recuperando tutte le informazioni aggiornate. Il vantaggio qui è che il Modello non sa nulla di Views (e perché dovrebbe farlo?) E può essere riutilizzato in altre situazioni, anche quelle senza Views.

Hai una situazione simile nel tuo gioco. Il GameEngine normalmente conosce GameState. Ma GameState non ha bisogno di sapere tutto su GameEngine, ma ha solo bisogno di accedere a determinati metodi di rendering su GameEngine. Ciò dovrebbe innescare un piccolo allarme nella tua testa che dice che (a) GameEngine sta provando a fare troppe cose all'interno di una classe e / o (b) GameState non ha bisogno dell'intero motore di gioco, ma solo della parte renderizzabile.

Tre approcci per risolvere questo includono:

  • Fai derivare GameEngine da un'interfaccia di rendering e passa quel riferimento nel GameState. Se mai si esegue il refactoring della parte di rendering, è sufficiente assicurarsi che mantenga la stessa interfaccia.
  • Fattorizza la parte di rendering in un nuovo oggetto Renderer e passalo invece a GameStates.
  • Lascialo così com'è. Forse a un certo punto vorrai accedere a tutte le funzionalità di GameEngine da GameState, dopo tutto. Ma tieni a mente che un software facile da mantenere e da estendere di solito richiede che ogni classe faccia riferimento al meno possibile all'esterno, motivo per cui suddividere le cose in sotto-oggetti e interfacce che eseguono un singolo e ben- compito definito è preferibile.

0

In genere si ritiene che sia buona prassi avere un'elevata coesione con un basso accoppiamento. Ecco alcuni link riguardanti l'accoppiamento e la coesione.

accoppiamento di Wikipedia

coesione di Wikipedia

basso accoppiamento, elevata coesione

StackOverflow sulle migliori pratiche

Avere due classi di riferimento reciproco significa avere un accoppiamento elevato. Il framework google guice mira a raggiungere un'elevata coesione con un basso accoppiamento attraverso l'iniezione di dipendenza. Ti suggerirei di leggere un po 'di più sull'argomento e di fare la tua chiamata in base al tuo contesto.

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.