Il metodo contrassegna come argomenti o come variabili membro?

8

Penso che il titolo "Il metodo contrassegna come argomenti o come variabili membro?" potrebbe essere subottimale, ma siccome mi manca una migliore terminologia atm., ecco qui:

Attualmente sto cercando di capire se i flag per un dato metodo di classe ( private ) dovrebbero essere passati come argomenti di funzione o tramite variabile membro e / o se c'è qualche modello o nome che copre questo aspetto e / o se questo suggerisce alcuni altri problemi di progettazione.

Per esempio (il linguaggio potrebbe essere C ++, Java, C #, non importa veramente IMHO):

class Thingamajig {
  private ResultType DoInternalStuff(FlagType calcSelect) {
    ResultType res;
    for (... some loop condition ...) {
      ...
      if (calcSelect == typeA) {
        ...
      } else if (calcSelect == typeX) {
        ...
      } else if ...
    }
    ...
    return res;
  }

  private void InteralStuffInvoker(FlagType calcSelect) {
    ...
    DoInternalStuff(calcSelect);
    ...
  }

  public void DoThisStuff() {
    ... some code ...
    InternalStuffInvoker(typeA);
    ... some more code ...
  }

  public ResultType DoThatStuff() {
    ... some code ...
    ResultType x = DoInternalStuff(typeX);
    ... some more code ... further process x ...
    return x;
  }
}

Quello che vediamo sopra è che il metodo InternalStuffInvoker prende un argomento che non è usato all'interno di questa funzione, ma è solo inoltrato all'altro metodo privato DoInternalStuff . (Dove DoInternalStuff verrà utilizzato privatamente in altri luoghi in questa classe, ad esempio nel metodo DoThatStuff (pubblico).)

Una soluzione alternativa sarebbe aggiungere una variabile membro che trasporta queste informazioni:

class Thingamajig {
  private ResultType DoInternalStuff() {
    ResultType res;
    for (... some loop condition ...) {
      ...
      if (m_calcSelect == typeA) {
        ...
      } ...
    }
    ...
    return res;
  }

  private void InteralStuffInvoker() {
    ...
    DoInternalStuff();
    ...
  }

  public void DoThisStuff() {
    ... some code ...
    m_calcSelect = typeA;
    InternalStuffInvoker();
    ... some more code ...
  }

  public ResultType DoThatStuff() {
    ... some code ...
    m_calcSelect = typeX;
    ResultType x = DoInternalStuff();
    ... some more code ... further process x ...
    return x;
  }
}

Soprattutto per le catene di chiamate profonde in cui il flag di selezione per il metodo interno viene selezionato all'esterno, l'uso di una variabile membro può rendere più pulite le funzioni intermedie, poiché non è necessario il parametro pass-through.

D'altra parte, questa variabile membro non rappresenta realmente alcuno stato dell'oggetto (poiché non è né impostato né disponibile all'esterno), ma è in realtà un argomento aggiuntivo nascosto per il metodo privato "interno".

Quali sono i pro e i contro di ciascun approccio?

    
posta Martin Ba 10.09.2012 - 13:26
fonte

4 risposte

15

On the other hand, this member variable isn't really representing any object state (as it's neither set nor available outside), but is really a hidden additional argument for the "inner" private method.

Ciò rafforza il mio istinto nel leggere la tua domanda: cerca di mantenere questi dati il più possibile locali, ovvero non aggiungerlo allo stato dell'oggetto . Questo riduce lo spazio degli stati della tua classe, rendendo il tuo codice più semplice da capire, testare e mantenere. Per non parlare del fatto che evitare lo stato condiviso rende la tua classe più a prova di bug - questo potrebbe o non potrebbe essere un problema per te in questo momento, ma potrebbe fare un'enorme differenza in futuro.

Naturalmente, se hai bisogno di aggirare eccessivamente tali bandiere, potrebbe diventare una seccatura. In questo caso, puoi prendere in considerazione

  • creando oggetti parametro per raggruppare insieme i parametri correlati in un singolo oggetto e / o
  • che incapsula questo stato e la logica associata in una famiglia separata di strategie . La validità di ciò dipende dal linguaggio: in Java, è necessario definire un'interfaccia distinta e una classe di implementazione (eventualmente anonima) per ogni strategia, mentre in C ++ o C # è possibile utilizzare i puntatori di funzione, i delegati o lambda, che semplificano il codice considerevolmente.
risposta data 10.09.2012 - 13:42
fonte
6

Voterò sicuramente per l'opzione "argomento": le mie ragioni:

  1. Sincronizzazione: cosa succede se il metodo viene chiamato da più thread contemporaneamente? Passando i flag come argomento, non sarà necessario sincronizzare l'accesso a questi flag.

  2. Testabilità - Immaginiamo che tu stia scrivendo un test unitario per la tua classe. Come testerai lo stato interno del membro? Cosa succede se il tuo codice genera un'eccezione e il membro termina in uno stato non previsto?

  3. Separazione delle preoccupazioni - Aggiungendo i flag agli argomenti del metodo, è chiaro che nessun altro metodo dovrebbe usarlo. Non lo userai per errore in qualche altro metodo.

risposta data 10.09.2012 - 13:43
fonte
3

La prima alternativa mi sembra OK. Una funzione è chiamata con un parametro e fa qualcosa che dipende da quel parametro . Anche se devi solo inoltrare il parametro a un'altra funzione, fai in modo che il risultato dipenda in qualche modo dal parametro.

La seconda alternativa sembra utilizzare una variabile globale, solo nell'ambito della classe anziché nell'ambito dell'applicazione. Ma è lo stesso problema ... devi leggere attentamente l'intero codice di classe per determinare chi e quando sta leggendo e scrivendo quei valori.

Quindi la prima alternativa vince.

Se dovessi usare troppi parametri (leggi: due o più) nella prima alternativa, che passano tutti insieme alla funzione successiva, puoi considerare di metterli in un oggetto (make è una classe interna, se non è rilevante per il resto dell'applicazione) e utilizza questo oggetto come parametro.

    
risposta data 10.09.2012 - 13:44
fonte
1

Voglio dire "nessuno".

Prima di tutto, considera che conosci già cosa farai quando chiami DoInternalStuff . La bandiera dice tanto. Quindi ora, invece di andare avanti e facendolo , stai scoreggiando chiamando una funzione che ora deve decidere cosa fare.

Se vuoi dire alla funzione cosa fare, allora digli cosa fare . È possibile passare una funzione effettiva che si desidera eseguire per ogni iterazione come delegato C # (o un oggetto funzione C ++ o una eseguibile Java o simile).

In C #:

class Thingamajig {
  private delegate WhateverType Behavior(ArgsType args);

  private ResultType DoInternalStuff(Behavior behavior) {
    ResultType res;
    for (... some loop condition ...) {
      ...
      var subResult = behavior(someArgs);
      ...
    }
    ...
    return res;
  }

  private void InteralStuffInvoker(Behavior behavior) {
    ...
    DoInternalStuff(behavior);
    ...
  }

  public void DoThisStuff() {
    ... some code ...
    InternalStuffInvoker(doThisStuffPerIteration);
    ... some more code ...
  }

  public ResultType DoThatStuff() {
    ... some code ...
    ResultType x = DoInternalStuff(doThatStuffPerIteration);
    ... some more code ... further process x ...
    return x;
  }
}

Finisci ancora con l'argomento che passa dappertutto, ma ti sbarazzi della maggior parte del se / else schifo in DoInternalStuff . Puoi anche memorizzare il delegato come campo, se lo desideri.

Per sapere se passare il todo-thingie in giro o conservarlo ... se devi sceglierne uno, quindi passarlo in giro. Archiviarlo probabilmente ti morderà nel culo in seguito, dato che hai praticamente trascurato la sicurezza del thread. Considera che se hai intenzione di fare qualcosa con i thread, ora devi bloccare per l'intera durata del ciclo, oppure qualcuno può passare da destra a destra. Considerando che se lo passi, è meno probabile che venga spostato nel cestino ... e se hai ancora bisogno di bloccare o qualcosa del genere, puoi farlo per parte di una iterazione anziché dell'intero ciclo.

Francamente, è bello che sia noioso. Probabilmente c'è un modo molto più semplice per fare ciò che vuoi fare, il che non potrei davvero consigliarti perché il codice non è ovviamente la cosa reale. La risposta migliore varia a seconda del caso.

    
risposta data 10.09.2012 - 23:03
fonte