La progettazione di un metodo che modifica l'argomento (se era oggetto) rappresenta una buona pratica?

3

La premessa utilizza un linguaggio (ad es. C #, javascript) che passa un oggetto per riferimento in un metodo.

Supponiamo che ci sia un oggetto "Player" che ha una proprietà "Level" e che un metodo prende Player come parametro e la sua funzione è di aumentare il livello del giocatore di 1.

Il metodo può essere progettato come qualcosa (c # sintassi):

void AddLevelBy1(Player player)
{
 player.Level +=1;
}

Il problema era che questo metodo ha cambiato il valore dell'argomento e non sono riuscito a trovare un modo generico per indicare (o potrebbe impedire?).

Si potrebbe ottenere qualche suggerimento dal nome del metodo "AddLevelBy1". Ma un modo del genere non è affidabile e se il metodo è il nome "pippo", allora diventa ancora più ambiguo.

Quindi c'è una soluzione / convenzione per questo caso (indicare se gli argomenti vengono modificati)? O una tale progettazione del metodo era considerata una cattiva pratica?

Aggiunto:

Prova a descrivere il mio caso più chiaramente:

  1. Ho un oggetto "Player" che non è stato definito da me (non può cambiare).

  2. Ho scritto un metodo (AddLevelBy1 () in questo esempio) che accetta "Player" come parametro (so cosa è successo).

  3. Trasmetto questo "Player" anche ad altri metodi (es. foo ()) che non sono stati scritti da me (quindi non è possibile sapere se il "Player" è stato modificato).

Grazie per alcune risposte di seguito, ma se i codici fossero stati progettati nel modo descritto sopra.

Ho aggiunto una demo del caso che ho descritto: link

    
posta Sun 14.03.2014 - 10:55
fonte

3 risposte

5

Se hai un metodo del genere, allora tale metodo dovrebbe in realtà essere parte della classe Player . Questo quindi indica chiaramente che il metodo potrebbe cambiare lo stato dell'oggetto.

class Player
{
    void AddLevelBy1()
    {
        this.level += 1;
    }
}

player.AddLevelBy1()

I problemi iniziano quando hai cambiato più di 1 oggetti, ma non c'è una soluzione generale a questo.

If using c#, the "out" keyword can be used to indicate the change.

Questo è sbagliato. La parola chiave out indica qualcosa di completamente diverso. Vedi di più qui: link

    
risposta data 14.03.2014 - 11:01
fonte
1

Sebbene il tuo esempio sia semplicistico e possa essere modellato in modo diverso (come suggerito da @Euphoric o usando metodi di estensione se la classe non è nella tua base di codice), ci sono molti casi d'uso in cui è legittimo che un metodo modifichi lo stato dei loro parametri.

Ad esempio, i processi di builder, i tuoi algoritmi ricorsivi, come cicli di rilevamento DFS .

Puoi usare Law of Demeter come regola empirica di cosa dovrebbe fare o non fare un metodo:

  • You can play with yourself.
  • You can play with your own toys (but you can’t take them apart),
  • You can play with toys that were given to you.
  • And you can play with toys you’ve made yourself.

Ovviamente, quando un metodo ha effetti collaterali, dovrebbe essere dichiarato nel suo nome: IncrementPlayerLevel(Player player) dichiara chiaramente che cambierà il livello del giocatore ...

Dall'altro lato delle considerazioni sulla progettazione, a volte è fondamentale che i metodi non cambino lo stato. Ad esempio, implementando un Actor model per i calcoli paralleli. Considerando questo, è meglio progettare gli oggetti di stato come immutable , quindi non devi chiederti se qualche metodo ha cambiato i tuoi oggetti senza che tu ne sia a conoscenza.

    
risposta data 14.03.2014 - 12:19
fonte
1

Come menzionato nella risposta di Uri , idealmente i tuoi tipi sarebbero immutabili. La mutabilità complica i programmi. Ma le lingue tradizionali hanno scarso supporto per l'immutabilità, e abbiamo una lunga storia di mutazione che risale ai giorni di assemblea, quindi a volte non si può andare avanti con la mutabilità. A volte la mutazione di un argomento è inevitabile - ad esempio, se la classe non è sotto il tuo controllo e non fornisce alcun modo di clonazione (cioè la copia profonda) dei suoi valori. Quindi, potrebbe non essere una buona pratica, ma a volte non è possibile aggirarla.

Alcuni hanno suggerito di rendere il mutatore un metodo della classe. Anche se la classe è sotto il tuo controllo, questo non ha necessariamente senso: c'è un numero infinito di cose che potresti voler fare con la tua classe mutevole, e quindi un numero infinito di funzioni che potrebbero aver bisogno di mutarlo. In secondo luogo, non tutto è un metodo. Se si ha un'operazione commutativa, non esiste un "destinatario" per la funzione; entrambi gli argomenti sono ugualmente validi. Se hai una funzione che funziona su argomenti di due tipi diversi, perché la funzione dovrebbe appartenere a una classe o l'altra? A volte questo non ha senso. Se non hai bisogno di accedere ai membri privati della classe, non ha senso rendere la funzione un metodo.

Ok, quindi è necessario modificare l'argomento. Cosa puoi fare per mitigare il danno potenziale? Come hai sottolineato, è fondamentale che il nome della funzione ti informi in modo inequivocabile che ciò accadrà. In secondo luogo, dovresti evitare di creare funzioni accessorie e mutanti allo stesso tempo . Se lo scopo di una funzione è di restituire alcune informazioni su un oggetto, le persone si sorprenderanno se muta anche quell'oggetto o qualsiasi altro.

Uri espone la Legge di Demetra, che è un'eccellente linea guida, ma tieni presente che è rilevante solo per le astrazioni. L'idea è che un'astrazione dovrebbe nascondere la sua implementazione, quindi catene come foo.bar.baz() sono di solito un segno che non stai facendo un ottimo lavoro lì. Ma non tutto è un'astrazione! Alcuni tipi (ad esempio tuple, record) sono semplicemente contenitori di dati e l'accesso ai loro membri non costituisce una violazione della legge, anche se il risultato finale è ancora una catena tratteggiata. (Naturalmente, se la tua astrazione non dovesse esporre quella tupla in primo luogo, è ancora un problema.)

    
risposta data 14.03.2014 - 13:59
fonte

Leggi altre domande sui tag