Spiegazione necessaria, per l'approccio "Chiedi, non dire"? [duplicare]

2

Sto seguendo un corso sui modelli di progettazione nell'ingegneria del software e qui sto cercando di capire il buono e il cattivo modo di progettazione relativo a "accoppiamento" e "coesione".

Non riuscivo a capire il concetto descritto nell'immagine seguente. L'esempiodelcodicemostratonell'immagineèambiguoperme,quindinonriescoacapirechiaramentecosaesattamente"Chiedi, non dire!" media Potresti spiegare per favore?

    
posta the_naive 09.11.2013 - 21:10
fonte

4 risposte

9

Il primo esempio indica l'oggetto in questione, "Ho bisogno di questi accessor e questi dati da quegli accessor". Potrebbe essere bello e buono, ma richiede che tu sappia come l'oggetto acceda a quelle cose.

Il secondo esempio chiede all'oggetto "Puoi fare questa cosa?", e quindi l'oggetto restituisce quell'operazione o fallisce. Non vi è alcun requisito di conoscenza interna dell'oggetto, facilitando così l'idea di accoppiamento libero poiché posso quindi modificare liberamente la funzionalità interal dell'oggetto sottostante fino a quando l'interfaccia accessor, "tum.addStudentToLecture (...), rimane la stessa .

    
risposta data 09.11.2013 - 21:19
fonte
1

"Chiedi" interruzioni di codice quando il suo oggetto cambia i dettagli della sua operazione. Deve indicare i tipi che l'oggetto restituirà, deve fare ipotesi sulla mutabilità e la sincronicità di tali ritorni e costringe altri oggetti ad implementare il codice "Chiedi" per fornire gli altri dettagli di cui hanno bisogno per cooperare.

Il codice "Tell", al contrario, mantiene tutti i tipi e le considerazioni sull'implementazione nelle mani dell'oggetto che può cambiarli. Quando cambiano, il codice "Tell" esterno non deve essere modificato per rimanere valido.

Per renderlo concreto usando il tuo esempio, diciamo che domani tu o un collaboratore:

  • È necessario rendere l'elenco degli studenti accessibile in modo asincrono e non è possibile utilizzare SortedList .

  • È necessario memorizzare gli studenti con chiavi diverse in contenitori multipli, quindi student.getKey() diventa ambiguo o fuorviante.

  • È necessario interrogare gli elementi di pse2013students di più di una semplice chiave.

Se hai usato il codice "Chiedi", devi rivedere sia l'oggetto tum che il metodo che lo utilizza nell'esempio. Se si utilizza il codice "Tell", è necessario solo apportare modifiche all'oggetto tum . Non sembra una grande differenza quando il tuo codice base è di due righe, ma in un programma non banale, può diventare un grave rischio di manutenibilità.

    
risposta data 10.11.2013 - 04:13
fonte
1

Nella progettazione orientata agli oggetti, le classi hanno a:

  • un'interfaccia pubblica che non cambia
  • implementazione nascosta che può essere modificata

Quindi, se cambi l'implementazione, ad esempio aggiungi nuove funzionalità, purché l'interfaccia funzioni ancora come prima, puoi essere certo che tutte le classi dipendenti continueranno a funzionare correttamente e non avrai bisogno di testare loro.

Quindi nell'esempio "cattivo", quando il codice "dice" all'oggetto tum come aggiungere uno studente alla lista. Ora le classi che invocano si scherzano con le parti interne di come viene mantenuto l'elenco degli studenti. Se l'autore dell'oggetto tum decide di sostituire:

getStudentList(Lectures.PSE,2013) 

con una nuova funzione

getStudentMap(Lectures.PSE, Campus.Main, 2013)

devi modificare il codice per farlo funzionare come prima, a causa di qualcosa che hanno fatto. L'aggiornamento di una libreria di utilità comune potrebbe diventare un incubo in quanto potrebbe causare la rottura di tonnellate di codice. Tuttavia, la libreria di utilità potrebbe avere alcune correzioni di sicurezza critiche, quindi potrebbe essere necessario aggiornarlo.

Nel buon esempio "chiedi", il codice richiama una funzione all'interno dell'oggetto tum per aggiungere uno studente alla lista. L'autore dell'oggetto tum sta mantenendo questa funzionalità ed è una loro responsabilità non tua. Questa è una buona cosa. Ora sono obbligati a farlo funzionare correttamente e se ora non sono obbligati a risolverlo, non tu.

Inoltre, potrebbero esserci altri requisiti che non sei a conoscenza di ciò che potrebbero fare. Per esempio, controllando i registri degli studenti e se questa è la prima lezione che uno studente frequenta, spedisci loro una lettera di benvenuto. La tua classe non avrebbe idea di ciò (o cura) e tuttavia ora, se diventasse un requisito, dovresti aggiungerlo e mantenerlo nel tuo codice.

    
risposta data 10.11.2013 - 15:40
fonte
0

"Tell, do not ask" lavora sulla premessa che un oggetto non dovrebbe essere obbligato a fare qualcosa in base ad alcune informazioni sull'oggetto stesso. Sicuramente l'oggetto dovrebbe conoscere il proprio stato? Non dovresti dover accoppiare codice al di fuori dell'oggetto per svolgere qualche funzione che potrebbe benissimo fare da solo. Ho trovato la foto di esempio terribilmente confusa, quindi userò un altro esempio.

Immagina di giocare a un gioco complesso come World of Warcraft. Sei un warlock e vuoi usare l'incantesimo Fear su qualche altro giocatore. L'incantesimo dovrebbe mandarli in esecuzione per 8 secondi. Se lo lanci di nuovo, quel tempo sarà dimezzato a causa di rendimenti decrescenti e così via. Ci sono molti fattori che decidono se l'incantesimo dovrebbe essere un successo o meno.

Ora, l'incantesimo Fear potrebbe essere implementato in questo modo:

function castFear(Player player) {
  if (player->isAPaladinAndFearsNothing())
    return false;
  if (player->hasTakenAntiFearPotion())
    return false;
  if (currentPlayer->getStat("shadow_magic_power") < player->getStat("shadow_magic_resistance"))
    return false;

  player->receiveFear();
}

Con "tell, do not ask" questo codice potrebbe essere semplificato drasticamente:

function castFear(Player player) {
  player->receiveFear(currentPlayer);
}

La classe Player potrebbe essere implementata in questo modo:

class Player {
  public boolean receiveFear(Player castingPlayer) {
    if (this->isAPaladinAndFearsNothing())
      return false;
    if (this->hasTakenAntiFearPotion())
      return false;
    if (castingPlayer->getStat("shadow_magic_power") < this->getStat("shadow_magic_resistance"))
      return false;

    // Get feared
  }
}

Ora, entrambe le soluzioni potrebbero sembrare abbastanza simili, ma se ci pensate, di chi è la responsabilità di sapere se il giocatore bersaglio può essere temuto o no? "Tell, do not ask" potrebbe apparire come un "trucco pulito", ma ho trovato questo stile di codifica come uno dei capisaldi di OOP. Non si dovrebbe sapere di quale complessità sia nascosta dietro quella funzione pubblica e come tale non dovrebbe nemmeno agire contro di essa. E se alcuni dei tuoi nuovi incantesimi dovessero anche temere il tuo obiettivo? Dovresti rimanere aggiornato su tutti i controlli che dovresti fare, ovunque. Se volevi implementare "Healing Horror" che teme il tuo obiettivo e ti guarisce, potresti semplicemente scrivere:

function castHealingHorror(Player player) {
  currentPlayer->health += 50;
  player->receiveFear(currentPlayer);
}

Direi che questa è anche una domanda su quale oggetto dovrebbe eseguire i controlli, il mittente o il destinatario? Ci pensiamo. Se dovessi inviare una lettera a un funzionario contenente una formulazione inappropriata, la tua lettera non verrebbe interrotta da qualche parte lungo la strada? Per quanto ne sai qualcuno potrebbe leggere la posta prima che il tuo destinatario lo faccia e filtrarlo come misura di sicurezza. Dovresti codice per questo come il mittente? Tutto quello che devi fare è prendere l'oggetto Letter preparato, darlo al postino e basta. Forse non raggiunge nemmeno l'ufficio postale di destinazione a causa di qualche inconveniente.

Esponiti alla minore complessità e informazione possibile e lascia che ogni componente gestisca le proprie regole aziendali. È quindi più facile combinarli in un sistema completamente incapsulato.

    
risposta data 10.11.2013 - 16:37
fonte

Leggi altre domande sui tag