Usa le interazioni del caso in un'architettura pulita

0

Sto sperimentando con Clean Architecture per il mio progetto di gioco strategico a turni. Ho casi d'uso che formano gerarchie e devono essere coordinati l'uno con l'altro. Vorrei alcuni consigli su come progettare queste interazioni.

Esempio

Il mio punto di partenza per il gioco è TurnUseCase . Fornisce informazioni sul turno corrente, l'elenco delle azioni possibili in quel turno e consente di terminare il turno. Il bit fine del turno è importante in quanto causa i miei problemi.

All'interno del turno, i giocatori possono eseguire varie azioni. Ognuno ha il suo caso d'uso, ad esempio MoveUseCase , AttackUseCase o DefendUseCase . Dopo alcune azioni, il turno dovrebbe finire.

Domanda

Come AttackUseCase dovrebbe passare le informazioni al TurnUseCase che il turno dovrebbe ora terminare? Senza questa informazione, TurnUseCase non può far avanzare il gioco su un altro giocatore.

  • Dovrei avere la dipendenza tra questi casi d'uso? Ho bisogno di una fabbrica per creare tutti questi casi d'uso e gestire le relazioni tra loro?
  • Devo usare Observer per notificare l'altro caso d'uso?
  • Devo creare una specie di Caso di utilizzo del sistema che controllerà le Entità e attiverà la fine del turno quando determinate condizioni sono soddisfatte (es .: il numero di azioni possibili è 0)? Questa logica potrebbe essere parte di TurnUseCase ?

Apprezzerei qualsiasi suggerimento su come risolvere questo problema.

Modifica

Ecco alcuni dettagli richiesti da @candied_orange per aiutare con la risposta.

Chi dovrebbe essere responsabile della creazione del caso d'uso? Non sembra efficiente creare casi d'uso da altri casi d'uso a causa delle dipendenze che il caso d'uso del bambino richiede (Presenter, Archivi, ecc.) . Allo stesso tempo, sono riluttante a spostare questa responsabilità perché questo fa parte del dominio (regole del gioco) per sapere quale operazione è possibile in un dato momento del gioco. La maggior parte degli esempi e degli articoli mostrano casi d'uso creati da Controller e solo situazioni in cui un caso d'uso opera indipendentemente da altri casi d'uso. Dalla risposta di Doc Brown capisco che questo tipo di problemi non sono affrontati dalle regole di Clean Architecture. Tuttavia vorrei vedere alcuni suggerimenti di persone che hanno utilizzato questo approccio in un sistema più grande.

Quale dovrebbe essere il ciclo di vita del caso d'uso? Nel mio particolare problema, creo dinamicamente tutti i casi d'uso all'avvio del turno. Ciò è dovuto in parte al fatto che il gioco è asimmetrico e che i diversi giocatori hanno diversi casi d'uso tra cui scegliere. Tuttavia, forse è necessario avere tutti i casi d'uso attivi in ogni momento e convalidare se il loro utilizzo è possibile in ogni dato momento del gioco?

Qual è il modo migliore per ignorare il caso d'uso? Questo tipo di operazione che ho pianificato di utilizzare per scopi tutorial in cui devo interrompere il normale flusso di gioco e spiegare le regole del gioco. Volevo che il tutorial decorasse il normale flusso di gioco in modo che i normali casi d'uso non fossero a conoscenza del tutorial. Per fare questo lavoro avrei bisogno di avere un posto dove vivono tutti i casi d'uso in modo da poterli sovrascrivere in runtime. Questo torna alla domanda su chi dovrebbe creare casi d'uso.

C'è un concetto di Sistema use case? Con ciò intendo un caso d'uso che viene attivato dal sistema piuttosto che da un utente. Questo torna alla domanda di Bart van Ingen Schenau sull'attivazione manuale dell'utente alla fine del turno . Sì, l'utente può farlo manualmente quando non vuole spostare nulla. Tuttavia, nello scenario normale, vorrei che il turno finisse automaticamente. Questo è anche perché ho altre situazioni in cui il caso di utilizzo del bambino deve fare qualcosa sul genitore quindi sono interessato a risolvere questo problema piuttosto che costringere l'utente a confermare manualmente la fine del turno.

    
posta grzegorz.kosciolek 09.12.2018 - 02:26
fonte

1 risposta

3

L' Architettura pulita non dice nulla sull'implementazione interfacce, dipendenze o interazioni tra diversi casi d'uso. I casi d'uso fanno tutti parte dello stesso anello. Quindi, in questa parte del sistema, sei da solo, devi iniziare a pensare da solo e prendere le tue decisioni su come implementerai le cose in modo significativo.

Alcune cose su cui devi iniziare a pensare:

  • Quali sono esattamente le regole del gioco (specialmente per terminare un turno) e come vengono rappresentate queste informazioni nel sistema? Senza chiarire questo requisito di base, non iniziare nemmeno a pensare a soluzioni diverse.

  • Come sarà il flusso di dati tra le parti? Ogni azione scriverà informazioni sulle modifiche allo stato del gioco su un'istanza globale? Ti piace una lezione di "gioco"? O un database? Un'istanza di terze parti (come il codice che gestirà il turno) può chiedere l'istanza globale su tali informazioni o quando è terminato un turno? Oppure questa istanza centrale non esiste e i diversi componenti devono comunicare direttamente tra loro?

  • In che modo vengono implementati i casi d'uso? Ogni caso d'uso è una classe a sé stante nel tuo sistema? Dove sono gli oggetti correlati MoveUseCase , AttackUseCase o DefendUseCase creati e qual è il loro tempo di vita? Il TurnUseCase li crea durante il turno? Chiama un metodo sincrono di ciascuno di questi oggetti per gestire il caso d'uso? Quindi dovrebbe essere abbastanza semplice per un oggetto TurnUseCase chiedere a qualsiasi altro oggetto qualsiasi informazione dopo ciascuna azione. O hai un sistema basato su eventi, in cui ogni caso d'uso comunica con gli altri attraverso canali di eventi asincroni? Quindi qualcosa di simile a un osservatore potrebbe essere appropriato.

  • Ci sarà qualcosa come un loop di gioco, magari in concomitanza con qualche tipo di coda di eventi ( vedi questo post )? Dove viene inserito nel tuo sistema e in che modo interagirà con gli altri componenti?

  • In che modo disaccoppiati hai bisogno di questi casi d'uso per scopi di test unitari? Questo dipende dalla complessità di ciascuno e dai test che intendi scrivere. Se ogni azione non è una classe, ma un metodo semplice in una classe TurnUseCase , probabilmente non avrai bisogno di alcuna misura di disaccoppiamento.

Come regola generale: inizia con il disegno più semplice a cui puoi pensare, e se risulta essere "troppo semplice" per le tue esigenze, quindi inizia il refactoring.

    
risposta data 09.12.2018 - 08:36
fonte

Leggi altre domande sui tag