Evitare l'accoppiamento

0

It is also true that a system may become so coupled, where each class is dependent on other classes that depend on other classes, that it is no longer possible to make a change in one place without having a ripple effect and having to make subsequent changes in many places.[1] This is why using an interface or an abstract class can be valuable in any object-oriented software project.

Citazione da Wikipedia

A partire da zero

Sto iniziando da zero con un progetto che ho terminato di recente perché ho trovato il codice troppo stretto e difficile da refactoring, anche quando si utilizza MVC. Userò MVC anche sul mio nuovo progetto ma voglio provare ad evitare le insidie questa volta, si spera con il tuo aiuto.

Riepilogo del progetto

Il mio problema è che desidero davvero mantenere il controller il più pulito possibile, ma sembra che non possa farlo. L'idea di base del programma è che l'utente scelga elenchi di parole inviati al motore di gioco. Sceglierà le parole a caso dagli elenchi finché non ne rimarrà nessuno.

Problema a portata di mano

Il mio problema principale è che il gioco avrà 'modalità', e deve controllare l'input in modi diversi attraverso un metodo chiamato checkWord() , ma esattamente dove metterlo e come estrarlo correttamente è una sfida per me . Sono nuovo per progettare modelli, quindi non sono sicuro che ce ne siano o potrebbero adattarsi al mio problema.

Il mio tentativo di astrazione

Ecco cosa ho ottenuto finora dopo ore di "refactoring" dei piani di progettazione, e so che è lungo, ma è il meglio che potrei fare per cercare di darti una panoramica (Nota: poiché questo è lo schizzo , qualsiasi cosa è soggetto a modifiche, tutti i consigli e l'aiuto sono ben accetti. Nota anche i punti di accoppiamento contrassegnati):

Lista di parole

class Wordlist {
    // Basic CRUD etc. here!
    // Other sample methods:
    public function wordlistCount($user_id) {} // Returns count of how many wordlists a user has
    public function getAll($user_id) {} // Returns all wordlists of a user
}

Parola

class Word {
    // Basic CRUD etc. here!
    // Other sample methods:
    public function wordCount($wordlist_id) {} // Returns count of words in a wordlist
    public function getAll($wordlist_id) {} // Returns all words from a wordlist
    public function getWordInfo($word_id) {} // Returns information about a word
}

Wordpicker

class Wordpicker {

    // The class needs to know which words and wordlists to exclude
    protected $_used_words = array();
    protected $_used_wordlists = array();

    // Wordlists to pick words from
    protected $_wordlists = array();

    /* Public Methods */
    public function setWordlists($wordlists = array()) {}
    public function setUsedWords($used_words = array()) {}
    public function setUsedWordlists($used_wordlists = array()) {}

    public function getRandomWord() {} // COUPLING POINT! Will most likely need to communicate with both the Wordlist and Word classes

    /* Protected Methods */
    protected function _checkAvailableWordlists() {} // COUPLING POINT! Might need to check if wordlists are deleted etc.
    protected function _checkAvailableWords() {} // COUPLING POINT! Method needs to get all words in a wordlist from the Word class

}

Gioco

class Game {

    protected $_session_id; // The ID of a game session which gets stored in the database along with game details
    protected $_game_info = array();

    // Game instantiation
    public function __construct($user_id) {
        if (! $this->_session_id = $this->_gameExists($user_id)) {
            // New game
        } else {
            // Resume game
        }
    }

    // This is the method I tried to make flexible by using abstract classes etc.
    // Does it even belong in this class at all?
    public function checkWord($answer, $native_word, $translation) {} // This method checks the answer against the native word / translation word, depending on game mode

    public function getGameInfo() {} // Returns information about a game session, or creates it if it does not exist
    public function deleteSession($session_id) {} // Deletes a game session from the database

    // Methods dealing with game session information
    protected function _gameExists($user_id) {}
    protected function _getProgress($session_id) {}
    protected function _updateProgress($game_info = array()) {}

}

Il gioco

/* CONTROLLER */
/* "Guess the word" page */

// User input
$game_type = $_POST['game_type']; // Chosen with radio buttons etc.
$wordlists = $_POST['wordlists']; // Chosen with checkboxes etc.

// Starts a new game or resumes one from the database
$game = new Game($_SESSION['user_id']);
$game_info = $game->getGameInfo();

// Instantiates a new Wordpicker
$wordpicker = new Wordpicker();

$wordpicker->setWordlists((isset($game_info['wordlists'])) ? $game_info['wordlists'] : $wordlists);
$wordpicker->setUsedWordlists((isset($game_info['used_wordlists'])) ? $game_info['used_wordlists'] : NULL);
$wordpicker->setUsedWords((isset($game_info['used_words'])) ? $game_info['used_words'] : NULL);

// Fetches an available word
if (! $word_id = $wordpicker->getRandomWord()) {

    // No more words left - game over!
    $game->deleteSession($game_info['id']);
    redirect();

} else {

    // Presents word details to the user
    $word = new Word();
    $word_info = $word->getWordInfo($word_id);

}

Il bit da finire

/* CONTROLLER */
/* "Check the answer" page */

// ??????????????????

( link )

Assicurati di attivare 'Larghezza del layout' a destra per una vista migliore. Grazie in anticipo.

Domande

  1. In che misura gli oggetti dovrebbero essere accoppiati liberamente? Se l'oggetto A ha bisogno di informazioni dall'oggetto B, come si dovrebbe ottenere questo senza perdere troppa coesione?
  2. Come suggerito nei commenti, i modelli dovrebbero contenere tutta la logica aziendale. Tuttavia, poiché gli oggetti dovrebbero essere indipendenti, dove incollarli insieme? Il modello dovrebbe contenere una sorta di area "indice" o "cliente" che collega i punti?

Modifica: Quindi, in pratica, quello che dovrei fare all'inizio è creare un nuovo modello che posso chiamare più facilmente con oneliner come $model->doAction(); // Lots of code in here which uses classes!

E il metodo per controllare le parole? Dovrebbe essere il proprio oggetto? Non sono sicuro di dove dovrei metterlo visto che è praticamente parte del 'gioco'. Ma d'altra parte, potrei semplicemente lasciare fuori l'astrazione e l'OOPness e renderlo un metodo del 'modello cliente' che sarà comunque incapsulato dal controller. Molto incerto su questo.

    
posta Seralize 26.11.2011 - 15:39
fonte

3 risposte

3

To which extent should objects be loosely coupled? If object A needs info from object B, how is it supposed to get this without losing too much cohesion?

Coupling and cohesion è un aspetto importante da tenere presente durante la scrittura del software. Tuttavia, idealmente il tuo codice è composto da aree di coesione elevate, liberamente accoppiate ad altre aree di coesione elevate. Non dovrebbe essere un obiettivo avere tutto il più liberamente possibile. Ha senso solo accoppiare in modo univoco quelle parti del software quando potrebbero essere usate indipendentemente dalle altre parti.

As suggested in the comments, models should hold all business logic. However, as objects should be independent, where to glue them together? Should the model contain some sort of "index" or "client" area which connects the dots?

Pensa a tutte le azioni che puoi fare sul modello. 'iniziare un gioco', 'terminare un gioco', 'unire un gioco', 'lanciare i dadi', ... Quelle sono azioni che dovrebbero essere esposte nel modello. Il controller lega la vista, dove ad es. viene premuto un pulsante per iniziare una partita e chiama l'azione pertinente nel modello.

Informazioni sul codice

A prima vista, non sono sicuro che tu abbia separato la tua intera logica di business dal tuo modello, e ne vedrai alcune in corso nel tuo controller.

Il tuo modello dovrebbe essere in grado di funzionare separatamente da solo facendo chiamate attraverso il controller che simulano le interazioni dell'utente.

    
risposta data 26.11.2011 - 16:15
fonte
1

Il tuo gioco (modello) incarna l'idea delle modalità comportamentali. Sai che il comportamento è nel dominio del controller, quindi sembra che il tuo modello guidi i tuoi controller, il che non può essere corretto.

Ma è in questo caso . Il problema è che stai parlando di due livelli di astrazione. Quando il tuo gioco è in una modalità particolare, dovresti avere triadi MVC: il controller ottiene l'interazione dell'utente, lo inoltra al modello, la vista riflette il nuovo stato del modello. A quel livello di astrazione, certamente non vuoi che gli oggetti del tuo Modello influenzino il tuo Controller.

Ma il tuo problema ha un livello superiore, in cui, in qualsiasi momento, è in una modalità. La selezione di un particolare insieme modale di triadi MVC dovrebbe essere sotto il controllo di un oggetto Modello di livello superiore (che, di per sé, fa parte di una triade MVC di livello superiore).

Devo ammettere che non seguo il tuo esempio particolare, ma prendiamo Soccer, dove hai una modalità normale e una modalità calci di rigore. Concettualmente, il tuo progetto di livello superiore sarebbe qualcosa del tipo:

[PSEUDOCODE]

//High-level model of game
class SoccerModel { 
   Controller activeController; 

   Controller normalController; 
   Controller penaltyKicksController;

   getActiveController()  { return activeController; } 

   chooseGameMode(evt : GameEvent) : Controller { 
         if ( evt == WHATEVER ) { 
              activeController = normalController;
         } else if ( evt == SOMETHING_ELSE ) { 
              activeController = penaltyKicksController;
         }
         return activeController; 
   }
}

interface IController { 
   //Returns an event in the Model domain
   eventForInput(input : SomeType) : GameEvent;
   //Deals with input, presumably by mapping it into a GameEvent and forwarding to Model
   handleInput(input : SomeType) : void;  
}

class GameController : IController {

   SoccerModel highLevelModel; //Terrible name, but is responsible for modeling the game
   ...etc...

   handleInput(myInput) { 
         var gameEvent = eventForInput(myInput);
         var modeController = highLevelModel.chooseGameMode(gameEvent);
         modeController.handleInput(myInput); //Maybe. Allows for more flexibility
   } 

   eventForInput(myInput) { 
         return model.getActiveController().eventForInput(); 
   }
}

L'oggetto Model di alto livello ( SoccerModel ) contiene i controller, ma a un livello inferiore. I controller che detiene ( penaltyKicksController normalController ) non hanno riferimenti circolari a SoccerModel.

    
risposta data 26.11.2011 - 19:59
fonte
0

Puoi dare un'occhiata a diversi modelli o elementi del Domain Driven Design (DDD) che potrebbero aiutarti:

Uno particolarmente buono è Services :

I servizi sono classi che appartengono al livello dominio (diciamo che il tuo modello in questo caso) e che mantengono quelle operazioni che non appartengono a nessuna delle tue entità (Evans, 2004)

Quindi, puoi creare interfacce per quelle operazioni che non appartengono alle tue classi operative CRUD e le implementazioni per tali interfacce sarebbero la colla che stavi cercando.

    
risposta data 28.04.2014 - 02:33
fonte

Leggi altre domande sui tag