Combinazione del metodo di modello con la strategia

14

Un incarico nella mia classe di ingegneria del software è progettare un'applicazione che può riprodurre forme diverse in un particolare gioco. Il gioco in questione è Mancala, alcuni di questi giochi sono chiamati Wari o Kalah. Questi giochi differiscono in alcuni aspetti, ma per la mia domanda è importante sapere che i giochi potrebbero differire in quanto segue:

  • Il modo in cui viene gestito il risultato di una mossa
  • Il modo in cui viene determinata la fine del gioco
  • Il modo in cui viene determinato il vincitore

La prima cosa che mi è venuta in mente di progettare questo è stato usare il modello di strategia, ho una variazione negli algoritmi (le regole reali del gioco). Il design potrebbe assomigliare a questo:

PoihopensatoamestessochenelgiocodiMancalaeWariilmodoincuivienedeterminatoilvincitoreèesattamentelostessoeilcodicesarebbeduplicato.Nonpensochequestosiaperdefinizioneunaviolazionedellaregola"una regola, un posto" o il principio di ESSERE VIVENTE visto che un cambiamento nelle regole di Mancala non significa automaticamente che anche la regola debba essere cambiata in Wari. Tuttavia dal feedback che ho ricevuto dal mio professore ho avuto l'impressione di trovare un design diverso.

Poi ho pensato a questo:

Ognigioco(Mancala,Wari,Kalah,...)avrebbesolounattributodeltipodiinterfacciadiogniregola,cioèWinnerDetermineresec'èunaversionediMancala2.0cheèlastessadiMancala1.0eccettopercomeililvincitoreèdeterminatopuòusaresololeversionidiMancala.

Pensochel'implementazionediquesteregolecomemodellostrategicosiacertamentevalida.Mailveroproblemaarrivaquandovoglioprogettarloulteriormente.

Nellaletturadelmodellodimetododelmodellohoimmediatamentepensatochepotesseessereapplicatoaquestoproblema.Leazionichevengonoeseguitequandounutenteeffettuaunamossasonosemprelestesseenellostessoordine,cioè:

  • depositalepietreneibuchi(questoèlostessopertuttiigiochi,quindisarebbeimplementatonelmetodostesso)
  • determinailrisultatodellospostamento
  • determinaseilgiocoèfinitoacausadellospostamentoprecedente
  • seilgiocoèfinito,determinachihavinto

Questiultimitrepassaggisonotuttinelmodellodistrategiadescrittosopra.Stoavendounsaccodiproblemiacombinarequestidue.Unapossibilesoluzionechehotrovatosarebbequelladiabbandonareilmodellodistrategiaefarequantosegue:

Non vedo davvero la differenza di design tra il modello di strategia e questo? Ma sono sicuro di aver bisogno di usare un metodo di template (anche se ero altrettanto sicuro di dover usare un modello di strategia).

Inoltre non riesco a determinare chi sarebbe responsabile della creazione dell'oggetto TurnTemplate , mentre con lo schema di strategia sento di avere famiglie di oggetti (le tre regole) che avrei potuto facilmente creare usando un pattern factory astratto. Avrei quindi un MancalaRuleFactory , WariRuleFactory , ecc. E creerebbero le istanze corrette delle regole e mi restituirebbero un oggetto RuleSet .

Diciamo che uso la strategia + modello di fabbrica astratto e ho un oggetto RuleSet che ha algoritmi per le tre regole in esso. L'unico modo in cui ritengo di poter ancora utilizzare lo schema del metodo template con questo è passare questo oggetto RuleSet al mio TurnTemplate . Il "problema" che emerge è che non avrei mai avuto bisogno delle mie implementazioni concrete di TurnTemplate , queste classi sarebbero diventate obsolete. Nei miei metodi protetti in TurnTemplate potrei semplicemente chiamare ruleSet.determineWinner() . Di conseguenza, la classe TurnTemplate non sarebbe più astratta ma dovrebbe diventare concreta, è quindi ancora un modello di metodo del modello?

Per riassumere, sto pensando nel modo giusto o mi manca qualcosa di facile? Se sono sulla strada giusta, come faccio a combinare un modello di strategia e un modello di metodo del modello?

    
posta Mekswoll 27.10.2012 - 22:25
fonte

4 risposte

6

Dopo aver esaminato i tuoi progetti, sia la prima che la terza iterazione sembrano essere disegni più eleganti. Tuttavia, dici che sei uno studente e il tuo professore ti ha dato un feedback. Senza sapere esattamente quale sia il tuo compito o lo scopo della classe o più informazioni su ciò che il tuo professore ha suggerito, prenderei tutto ciò che dico di seguito con un pizzico di sale.

Nel tuo primo progetto, dichiari il tuo RuleInterface come un'interfaccia che definisce come gestire il turno di ogni giocatore, come determinare se il gioco è finito e come determinare un vincitore dopo che il gioco è terminato. Sembra che sia un'interfaccia valida per una famiglia di giochi che presenta variazioni. Tuttavia, a seconda dei giochi, potresti avere un codice duplicato. Sono d'accordo che la flessibilità nel cambiare le regole di un gioco sia una buona cosa, ma direi anche che la duplicazione del codice è terribile per i difetti. Se copi / incolli il codice difettoso tra le implementazioni e uno ha un bug in esso, ora hai più bug che devono essere corretti in posizioni diverse. Se si riscrivono le implementazioni in momenti diversi, è possibile introdurre difetti in posizioni diverse. Nessuno di questi è desiderabile.

Il tuo secondo progetto sembra piuttosto complesso, con un albero di eredità profonda. Almeno, è più profondo di quanto mi aspetterei per risolvere questo tipo di problema. Stai anche iniziando a suddividere dettagli di implementazione in altre classi. In definitiva, stai modellando e realizzando un gioco. Questo potrebbe essere un approccio interessante se ti venisse richiesto di combinare le tue regole per determinare i risultati di una mossa, la fine del gioco e un vincitore, che non sembra essere nei requisiti che hai menzionato . I tuoi giochi sono serie di regole ben definite e cercherò di incapsulare i giochi il più possibile in entità separate.

Il tuo terzo design è uno che mi piace di più. La mia unica preoccupazione è che non sia al livello giusto dell'astrazione. In questo momento, sembra che tu stia modellando una svolta. Consiglierei di prendere in considerazione la progettazione del gioco. Considera che hai giocatori che stanno facendo mosse su una specie di tavola, usando pietre. Il tuo gioco richiede che questi attori siano presenti. Da lì, il tuo algoritmo non è doTurn() ma playGame() , che va dallo spostamento iniziale alla mossa finale, dopo di che termina. Dopo la mossa di ogni giocatore, regola lo stato del gioco, determina se il gioco è in uno stato finale, e se lo è, determina il vincitore.

Consiglierei di dare un'occhiata più ravvicinata al tuo primo e terzo progetto e di lavorare con loro. Potrebbe anche aiutare a pensare in termini di prototipi. Come sarebbero i client che usano queste interfacce? Un approccio progettuale ha più senso per l'implementazione di un client che sta effettivamente creando un'istanza di un gioco e il gioco? Devi capire con cosa sta interagendo. Nel tuo caso particolare, è la classe Game e tutti gli altri elementi associati: non puoi progettare separatamente.

Dal momento che dici di essere uno studente, mi piacerebbe condividere alcune cose da un periodo in cui ero l'AT per un corso di progettazione software:

  • I pattern sono semplicemente un modo di catturare cose che hanno funzionato nel passato, ma di astrarle in un punto in cui possono essere utilizzate in altri progetti. Ogni catalogo di modelli di progettazione dà un nome a un modello, spiega le sue intenzioni e dove può essere utilizzato, e le situazioni in cui finirebbe per limitare il design.
  • Il design nasce con l'esperienza. Il modo migliore per diventare bravi nel design non è semplicemente concentrarsi sugli aspetti della modellazione, ma realizzare ciò che accade nell'implementazione di quel modello. Il design più elegante è utile se non può essere facilmente implementato o non si adatta al design più grande del sistema o ad altri sistemi.
  • Pochissimi disegni sono "giusti" o "sbagliati". Finché il design soddisfa i requisiti del sistema, non può essere sbagliato. Una volta che c'è una mappatura di ogni requisito in una rappresentazione di come il sistema soddisferà tale requisito, il design non può essere sbagliato. A questo punto è solo un punto di vista qualitativo su concetti come flessibilità o riusabilità o testabilità o manutenibilità.
risposta data 28.10.2012 - 14:43
fonte
3

La tua confusione è giustificata. Il fatto è che i modelli non si escludono a vicenda.

Il metodo Template è la base per molti altri pattern, come Strategy e State. Essenzialmente, l'interfaccia della strategia contiene uno o più metodi di template, ognuno dei quali richiede che tutti gli oggetti che implementano una strategia abbiano (almeno) qualcosa come un metodo doAction (). Questo permette alle strategie di essere sostituite l'una con l'altra.

In Java, un'interfaccia non è altro che un insieme di metodi di template. Allo stesso modo, qualsiasi metodo astratto è essenzialmente un metodo di modello. Questo modello (tra gli altri) era ben noto ai progettisti della lingua, quindi lo hanno integrato.

@ThomasOwens offre ottimi consigli per affrontare il tuo particolare problema.

    
risposta data 28.10.2012 - 16:20
fonte
0

Se ti senti distratto dai modelli di progettazione, il mio consiglio per te è di prototipare il gioco prima che i pattern ti saltino addosso. Non penso sia davvero possibile o consigliabile provare a progettare un sistema perfettamente prima e poi implementarlo (in un modo simile trovo perplesso quando le persone provano a scrivere interi programmi prima e poi a compilare, piuttosto che a farlo un po 'alla volta .) Il guaio è che è improbabile che tu possa pensare ad ogni scenario che la tua logica dovrà affrontare e, durante la fase di implementazione, perderai ogni speranza o tenterai di attenersi al tuo design originale imperfetto e introduci degli hack, o addirittura peggio consegnare nulla.

    
risposta data 28.10.2012 - 02:21
fonte
-1

Andiamo ai chiodini. Non c'è assolutamente bisogno di alcuna interfaccia di gioco, nessun modello di progettazione, nessuna classe astratta e nessuna UML.

Se hai una quantità ragionevole di classi di supporto, come l'interfaccia utente, la simulazione e qualsiasi altra cosa, in pratica tutto il tuo codice specifico per la logica di gioco viene comunque riutilizzato. Inoltre, il tuo utente non cambia il suo gioco in modo dinamico. Non capovolgi a 30Hz tra i giochi. Giochi una partita per circa mezz'ora. Quindi il tuo polimorfismo "dinamico" non è affatto dinamico. È piuttosto statico.

Quindi il modo sensato per andare qui è usare un'astrazione funzionale generica, come Action di C # o std::function di C ++, creare un Mancala, un Wari e una classe Kalah, e andare da lì.

std::unordered_map<std::string, std::function<void()>> games = {
    { "Kalah", [] { return Kalah(); } },
    { "Mancala", [] { return Mancala(); } },
    { "Wari", [] { return Wari(); } }
};
void play() {
    std::function<void()> f;
    f = games[GetChoiceFromUI()];
    f();
    if (PlayAgain()) return play();
}
int main() {
    play();
}

Fatto.

Non chiami i giochi. I giochi ti chiamano.

    
risposta data 28.10.2012 - 00:10
fonte

Leggi altre domande sui tag