Mantenimento della separazione delle preoccupazioni

8

Sto realizzando la mia prima app C # e sto riscontrando un po 'di difficoltà con la separazione delle preoccupazioni. Capisco il concetto, ma non so se lo sto facendo bene. Ho questo come un rapido esempio per illustrare la mia domanda. In un'app come un gioco, esiste una classe principale che esegue il ciclo principale, come Programma o Gioco. La mia domanda è: mantengo ogni riferimento ad ogni istanza di una classe in questa classe e faccio che sia l'unico modo in cui interagiscono?

Ad esempio, un gioco di carte con un giocatore, carte e una tavola. Diciamo che il giocatore vuole mettere le carte sulla scacchiera, ma la classe Player ha solo una lista < > di carte e nessuna idea riguardo al tabellone di gioco. Tuttavia, la principale classe di gioco conosce i giocatori, le carte e il tabellone. Dovrebbe essere la classe di gioco a mettere le carte sul tavolo, o ha più senso che, poiché è l'azione del giocatore, dovrebbe essere all'interno della classe Player.

Esempio:

public class Game{
    private GameBoard gameBoard;
    private Player[] players;

   public Game(){
     gameBoard = new GameBoard(10,10);
     Player player1 = new Player();
     Player player2 = new Player();
     players = {player1, player2};
   }

   // Create method here?
   public void PlayerPlaceCard(int x, int y, int cardIndex){
      gameBoard.grid[1,1] = player1.cards[cardIndex];
   }
}

public class Player {
     public List<Cards> cards = new List<Cards>();

     public Player(){
     }

     // Or place method here?
     public PlaceCard(Card card, int x, int y, GameBoard gameBoard){
     }
}

public class GameBoard{
    public Card[,] grid;

    public GameBoard(int x, int y){
       // Make the game board
    }
}

public class Card{
   public string name;
   public string value;
}

Per me ha più senso avere il metodo nel gioco, perché il gioco sa tutto. Ma mentre aggiungo altro codice, Game sta diventando piuttosto gonfio e sto scrivendo molte funzioni di PlayerDoesThis ().

Grazie per qualsiasi consiglio

    
posta user2410532 24.07.2015 - 04:35
fonte

1 risposta

12

La chiave qui non è solo separazione delle preoccupazioni , ma anche principio di responsabilità singola . I due sono lati fondamentalmente diversi della stessa medaglia: quando penso che il SOC ritengo top-down (ho queste preoccupazioni, come li separo?) Mentre SRP è più dal basso (ho questo oggetto, ha un preoccupazione singola? Dovrebbe essere divisa? Le sue preoccupazioni sono già troppo divise?)

Nel tuo esempio hai le seguenti entità e le loro responsabilità:

  • Gioco: questo è il codice che rende il programma "vai".
  • GameBoard: mantiene lo stato dell'area di gioco.
  • Carta: una singola entità sul tabellone.
  • Giocatore: esegue azioni che cambiano lo stato del tabellone.

Una volta che pensi alla singola responsabilità > di ciascuna entità, le linee diventano più chiare.

In an app such as a game, there is a main class that runs the main loop, such as Program or Game. My question is, do I maintain every reference to every instance of a class in this class, and make that the only way in which they interact?

Qui ci sono davvero due problemi da tenere a mente. La prima cosa da decidere è quali entità conoscono altre entità? Quali entità appartengono ad altre entità?

Guarda le responsabilità che ho delineato sopra. I giocatori eseguono azioni che cambiano lo stato del tabellone. In altre parole, i giocatori inviano messaggi a (chiama i metodi su) il tabellone. Questi messaggi riguardano probabilmente le carte: per esempio, un giocatore può mettere una carta in mano sul tabellone, o cambiare lo stato di una carta esistente (ad esempio girare una carta o spostarla in una nuova posizione).

Chiaramente, un giocatore deve conoscere il tabellone di gioco che contraddice l'assunto che hai fatto nella tua domanda. Altrimenti, il giocatore deve inviare un messaggio al gioco, che quindi trasmette quel messaggio al tabellone. Poiché i giocatori eseguono azioni su il tabellone di gioco, i giocatori devono essere a conoscenza del tabellone. Questo aumenta l'accoppiamento: invece che il giocatore che invia il messaggio direttamente, ora due attori devono sapere come inviare quel messaggio. La Legge di Demetra implica che se un oggetto deve agire su un altro oggetto, in questo scenario, quell'altro oggetto deve essere passato in tramite parametro per ridurre l'accoppiamento.

Quindi, dove memorizzi quale stato? Il gioco è il driver qui, deve creare tutti gli oggetti direttamente o tramite proxy (ad esempio una fabbrica o un costruttore che chiama il gioco). La seguente domanda logica è: quali oggetti hanno bisogno di quali altri oggetti? Questo è fondamentalmente quello che ho chiesto sopra, ma un modo diverso di chiedermelo.

Il modo in cui lo progetterei è come questo:

  • Il gioco crea tutti gli oggetti necessari per il gioco.

  • Il gioco mescola le carte e le divide in base al gioco che rappresenta (poker, solitario, ecc.)

  • Il gioco posiziona le carte nelle loro posizioni iniziali: forse alcune sulla plancia di gioco, altre nelle mani dei giocatori.

  • Il gioco entra quindi nel suo ciclo principale che rappresenta un turno.

Ogni turno sarebbe simile a questo:

  • Il gioco invia un messaggio a (invoca un metodo su) il giocatore corrente e fornisce un riferimento al tabellone.

  • Il giocatore esegue qualsiasi logica interna (lettore di computer) o interazione dell'utente necessaria per determinare quale gioco eseguire.

  • Il giocatore manda un messaggio alla plancia di gioco fornita chiedendogli di cambiare lo stato della scheda di gioco.

  • Il tabellone di gioco decide se il movimento è valido o meno (è responsabile per mantenere lo stato di gioco valido).

  • Il controllo ritorna al gioco, che poi decide cosa fare dopo. Verifica le condizioni di vittoria? Disegnare? Prossimo giocatore? Prossima svolta? Dipende dallo specifico gioco di carte che si sta giocando.

Should it be up to the Game class to place the cards on the board, or does it make more sense that, because it is the player's action, it should be within the Player class.

Entrambi: il gioco è responsabile della configurazione iniziale, ma il giocatore esegue le azioni alla lavagna. GameBoard è responsabile della garanzia di uno stato valido. Ad esempio, nel solitario classico, solo la prima carta di una pila può essere scoperta.

Ritorno al punto originale: hai le giuste separazioni di preoccupazioni. Hai identificato gli oggetti giusti. Ciò che ti ha inciampato è stato capire come i messaggi fluiscono attraverso il sistema e quali oggetti dovrebbero mantenere i riferimenti ad altri oggetti. Lo progetterei in questo modo, che è pseudocodice:

class Game {
  main();
}

class GameBoard {
  // Data structures specific to the game being played. There is a
  // lot of hand-waving here to give the general idea without
  // getting bogged down in the implementation.
  Map<Card, Location> cards;

  GameBoard(Map<Card, Location>);

  // Return false if the move is invalid.
  bool flip(Card);
  bool move(Card, Location);
}

class Card {
  // Make Rank and Suit enums.
  Suit suit;
  Rank rank;
  bool faceUp;
}

class Player {
  Set<Card> hand;

  Player(Set<Card>);
  void takeTurn(GameBoard);
}
    
risposta data 24.07.2015 - 06:43
fonte

Leggi altre domande sui tag