Modelli di progettazione quando la classe ha bisogno di consapevolezza esterna

4

Quando mi è stato insegnato per la prima volta i principi orientati agli oggetti, mi è stato insegnato che quando si utilizzano oggetti in una relazione "hasA" (o situazioni simili in cui un oggetto helper incapsula uno scopo più piccolo discreto in un sistema più grande), la classe posseduta non dovrebbe avere consapevolezza del suo ambiente più grande, quindi, ad esempio:

  • Il mazzo di classe ha una carta [], ogni carta non ha accesso diretto al mazzo
  • Class GameBoard ha GamePiece, ogni GamePiece non ha accesso verso la scacchiera

Questo ha tenuto bene e vero per un po ', ma alla fine ho avuto un problema in cui sono strongmente tentato di uscire da questo.

In un'applicazione in cui sono definiti i "comandi" definiti dall'utente che vengono analizzati dall'input ed eseguiti, ha un senso relativo definire Comando di classe e Condizionale di classe, ognuno dei quali ha sottoclassi per ciascuno dei molti possibili tipi di comando che l'utente può definire .

Contesto aggiunto Questa è un'utilità di gioco di ruolo, quindi ogni comando si riferisce a un'operazione eseguita sull'oggetto "Carattere" o a un problema con le regole dei generatori. Quindi AddStat (WIL, 5) incrementa di conseguenza quel valore. I comandi non dovrebbero avere bisogno di accedere al generatore più grande per eseguire se stessi, ma dovranno almeno accedere all'oggetto modificato dal comando rappresentato. Quindi per la maggior parte degli scopi, il personaggio è l'"ambiente" su cui il comando agisce.

Ha senso, sembra a prima vista aiutare le cose. Ma c'è un collegamento mancante quando molte di queste classi possono solo eseguire validazioni di input di base senza un contesto più ampio, per non parlare del fatto che senza più oggetti "consapevoli", ogni parte del codice che le riguarda dovrà fare quella principale ifelseif branch per capire quale comando ha a che fare con ...

La cosa del design non sono sicuro di dove andare qui è la seguente (questo non è ancora codificato a causa della quantità di modifiche che ci vorrebbe per adattare una di queste soluzioni divergenti)

  • Il comando rimane "stupido" senza consapevolezza di carattere / ambiente. È consentito un metodo di risoluzione (Character obj), che consente di trasmettere una consapevolezza sufficiente a restituire un vero / falso significativo. Ciò significa che Command rimane un validatore di sintassi di base relativamente semplice, mentre il codice di corpo in altri casi fa il lavoro pesante per eseguire
  • Come prima, eccetto Command ottiene anche il proprio metodo di risoluzione (Character obj), e ogni sottoclasse contiene il codice necessario per eseguire il comando
  • Come prima, tranne che creiamo completamente oggetti Command con un riferimento al Carattere

Non sono sicuro di dove andare su questo ... Mentre tecnicamente c'è già un po 'di organizzazione del codice ottenuta con l'opzione più semplice, si ritiene che la leggibilità dell'applicazione trarrebbe beneficio dall'opzione 2 / Opzione 3, anche se il il purismo dell'incapsulamento soffre.

Quale percorso è il modo per andare qui? O sono completamente fuori dal marchio?

    
posta Vigilant 10.09.2015 - 10:18
fonte

2 risposte

5

La tua esperienza di apprendimento è un esempio di situazione da manuale, in cui ti viene insegnato che qualcosa è sbagliato senza essere istruito perché è sbagliato. Mentre questo è facile da fare per l'insegnante e più facile da ricordare per lo studente, sarà presto messo in discussione da una situazione in cui è troppo limitante. E sapere perché esiste questa regola ti permetterebbe di valutare i pro e i contro del rispetto di questa regola.

Quindi perché "la classe di proprietà non dovrebbe avere consapevolezza del suo ambiente più grande"? Perché creerebbe dipendenza ciclica. Nel tuo esempio, il mazzo dipende dalla carta e la carta dipendono dal mazzo. Diventa quindi impossibile poterne usare uno senza l'altro o viceversa. Dal punto di vista della modularità, sia Deck che Card per singolo "aggregato" logico e dovrebbero essere visualizzati come tali dal resto del codice. Ciò riduce la modularità e la manutenibilità, quindi è soprattutto una cattiva idea.

MA! A volte ha senso che più classi formino un aggregato. Il caso di Deck e Cards che si riferiscono l'uno con l'altro avrebbe senso se il resto dell'applicazione non funziona mai solo con le carte senza riferimento al mazzo a cui appartengono le carte. In questo modo, quando la vita del Deck termina, anche quella di Card.

Quindi nel tuo caso, se non ti aspetti che i Comandi sopravvivranno all'Ambiente o al Giocatore, allora non vedo alcun problema nel riferirli.

    
risposta data 10.09.2015 - 11:12
fonte
2

Il principio che descrivi è una linea guida, non una regola concreta.

Considera il caso in cui hai un mazzo di carte non standard: cioè, le carte non sono {Clubs, Picche, Quadri, Cuori} / {Asso-Re}, ma piuttosto le carte sono per uno dei moderni, giochi di carte collezionabili come Pokémon, Yu-Gi-Oh o Magic the Gathering, in cui le carte hanno abilità speciali.

In questo caso, supponi di aver impostato un sistema in cui ogni Card ha un void use(...) che esegue la sua azione. Hai classi che rappresentano un Deck di carte, un Hand di carte e forse anche altri contenitori di carte (scarto, qualunque sia).

Alcune carte possono avere un'abilità che li fa rimuovere dalla tua mano di carte, o che bersagliano altre carte e le rimuove dalla mano o dal mazzo in cui sono contenute. Alcune carte hanno anche la capacità di rimuovere completamente se stessi dal gioco, nel qual caso una carta potrebbe dover raggiungere completamente il Game che incapsula tutto quanto sopra. Game ha un Player[] , Player ha un Deck e un Hand forse, e così via, e questa carta speciale deve fare riferimento almeno al% digetOwner().remove(this) del mazzo o della mano, se non il anche il giocatore e il gioco. Ricorda che Deck (o Hand ) non ha idea che Card abbia una capacità speciale di rimuovere se stesso dal suo contenitore poiché probabilmente non hai questa nozione nell'interfaccia di Card .

Per trovare qualche altra soluzione per questo che ti permetta di evitare di dare Card una proprietà owner potrebbe aggiungere più complessità di quanto valga e rendere il sistema meno gestibile piuttosto che l'aumentata manutenibilità che speravi di ottenere seguire ciecamente una regola concreta trasmessa dai tuoi insegnanti. Questo aumento di complessità potrebbe essere particolarmente vero se si sta mantenendo una base di codice legacy di grandi dimensioni.

Così vedi, anche nel classico esempio di "A Card non dovrebbe fare riferimento al suo Deck " ci sono delle eccezioni.

Se il tuo framework ti consente di evitare facilmente l'accoppiamento di Card con Deck , preferisci evitare l'accoppiamento. Se non esiste un modo ragionevole all'interno del framework su cui stai lavorando, e se l'accoppiamento di Card con Deck è l'opzione migliore, allora dovrebbe accoppiarsi; semplicemente non farlo con leggerezza.

Quando decidi di farlo, non pensare solo a quanto sia più facile sviluppare l'attività corrente. Fai del tuo meglio per pensare a come un futuro manutentore del codice (forse tu, forse no) reagirà quando sarà necessario aggiungere ulteriori funzionalità.

    
risposta data 21.03.2017 - 22:52
fonte

Leggi altre domande sui tag