Vari metodi che manipolano la stessa variabile membro contro ogni prende input e fornisce output [closed]

0

Come paradigma del software design che è meglio? lasciare che vari metodi manipolino una variabile membro o definisca ogni metodo o funzione per prendere alcuni input e fornire alcuni output?

Ad esempio

   class Test {

       void FooMethod1()
       {
           Foo = ...
       }
       void FooMethod2()
       {
           Foo = ...
       }
       void Method3()
       {
           FooMethod1();
           FooMethod2()
       }
    }

rispetto.

  class Test {

       var FooMethod1(var input)
       {
           input = ...
           return ...
       }
       var FooMethod2(var input)
       {
           input = ...
           return ...
       }
       void Method3()
       {
           var Foo;
           FooMethod1(Foo);
           FooMethod2(Foo)
       }
    }
    
posta Ahmad 03.12.2014 - 17:14
fonte

3 risposte

2

Stai confondendo le variabili globali per le variabili membro.

La modifica delle variabili membro nelle funzioni membro va bene, quando la funzione ha lo scopo di modificare lo stato della classe. Questo è il motivo per cui OOP è nato in primo luogo: raggruppare logicamente stato e comportamento. Se sta diventando difficile tenere traccia di ciò che sta accadendo alle variabili membro, non è un problema usarle, il problema è che la tua classe è troppo grande.

Se si stanno passando variabili ininterrottamente, si potrebbe anche non utilizzare alcuna funzionalità OOP.

Un'altra cosa da considerare, è che se la modifica delle variabili membro fosse considerata imprudente, allora quale sarebbe il punto di una funzione setter?

Diamo un'occhiata a un esempio di buffer circolare, scritto in C, barrato da Pagina Wiki :

/* Opaque buffer element type.  This would be defined by the application. */
typedef struct { int value; } ElemType;

/* Circular buffer object */
typedef struct {
    int         size;   /* maximum number of elements           */
    int         start;  /* index of oldest element              */
    int         end;    /* index at which to write new element  */
    ElemType   *elems;  /* vector of elements                   */
} CircularBuffer;

void cbInit(CircularBuffer *cb, int size) {
    cb->size  = size + 1; /* include empty elem */
    cb->start = 0;
    cb->end   = 0;
    cb->elems = calloc(cb->size, sizeof(ElemType));
}

void cbFree(CircularBuffer *cb) {
    free(cb->elems); /* OK if null */
}

int cbIsFull(CircularBuffer *cb) {
    return (cb->end + 1) % cb->size == cb->start;
}

int cbIsEmpty(CircularBuffer *cb) {
    return cb->end == cb->start;
}

/* Write an element, overwriting oldest element if buffer is full. App can
   choose to avoid the overwrite by checking cbIsFull(). */
void cbWrite(CircularBuffer *cb, ElemType *elem) {
    cb->elems[cb->end] = *elem;
    cb->end = (cb->end + 1) % cb->size;
    if (cb->end == cb->start)
        cb->start = (cb->start + 1) % cb->size; /* full, overwrite */
}

/* Read oldest element. App must ensure !cbIsEmpty() first. */
void cbRead(CircularBuffer *cb, ElemType *elem) {
    *elem = cb->elems[cb->start];
    cb->start = (cb->start + 1) % cb->size;
}

L'esempio C passa tutto via argomenti, trasportato attraverso una struttura. Questo perché C non offre funzioni OOP integrate, quindi è quello che devi fare.

Se lo facciamo in C ++, rendere tutto nella struct una variabile membro e accedervi 'globalmente' è del tutto appropriato:

class CircularBuffer
{
  private:
    int         size;   // maximum number of elements           
    int         start;  // index of oldest element              
    int         end;    // index at which to write new element  
    ElemType   *elems;  // vector of elements                   
  public:
    CircularBuffer(int cbSize) {
      size = cbSize + 1; // include empty elem
      start = 0;
      end = 0;
      elems = new ElemType[size];
    }

    ~CircularBuffer() {
      delete[] elems;
    }

    bool IsFull() {
      return (end+1) % size == start;      
    }

    bool IsEmpty() {
      return end == start;
    }

    void Write(ElemType *elem) {
      elems[end] = *elem;
      end = (end + 1) % size;
      if (end == start)
        start = (start + 1) % size; //full, overwrite
    }

    //Read oldest element. App must ensure !IsEmpty() first.
    void Read(ElemType *elem) {
        *elem = elems[start];
        start = (start + 1) % size;
    }
}

Se invece avevi metodi che facevano qualcosa del genere:

  void Write(ElemType *elem)
  {
    _Write(ElemType *elem, this->start, this->end, this->size);
  }

o peggio, dovevo chiamare il membro in questo modo:

  cb.Write(&elem,cb.start,cb.end,cb.size);

ci sarebbe molta rabbia.

Il punto è che le variabili start, end, size e elems fanno parte dello stato interno della classe, e quindi possono essere manipolate direttamente all'interno dei metodi delle classi. L'utente della classe può manipolarli solo attraverso le funzioni esposte, come parte di Information Hiding . Se stai solo passando le variabili a tutte le funzioni membro, come se scrivessi C, allora potresti anche usare C correttamente invece di compilarlo con un compilatore C ++.

    
risposta data 03.12.2014 - 19:28
fonte
3

Il primo è leggermente migliore, ma in generale, dovresti cercare di evitare del tutto gli effetti collaterali non necessari.

Perché? La modifica dei parametri di input è vile. È inaspettato, è difficile da testare, è difficile da riutilizzare, crea problemi in ambienti concorrenti ...

La modifica di una classe a volte va bene, questo è ciò che le classi sono veramente ... ma può essere difficile da testare, difficile da modificare / estendere, crea problemi in ambienti concorrenti ... E tu usi qui dove Foo è solo un segnaposto da passare in Method3 che sconfigge il punto della classe.

Un approccio migliore sarebbe simile a questo:

Foo Method1(Foo x){ ... }
Foo Method2(Foo x){ ... }
Foo Method3(){
    Foo stuff = ...;
    stuff = Method1(stuff);
    stuff = Method2(stuff);
    // and return or just do stuff with stuff...
}
    
risposta data 03.12.2014 - 17:24
fonte
1

Il primo caso è estremamente migliore. MA solo fino a quando la classe contenente è coesiva. L'uso della prima opzione quando la classe ha molte responsabilità diverse introdurrà associazioni indesiderate che rendono la comprensione del flusso del comportamento della classe peggiore rispetto al secondo caso.

Inoltre, il primo approccio avrà un vantaggio, quando si hanno molte variabili diverse con cui lavorare e quando diverse funzioni potrebbero assumere variabili diverse. Ciò significherebbe che tutte le funzioni dovrebbero ottenere tutte le variabili, anche se solo poche sono utilizzate dalla funzione stessa e il resto è passato in altri metodi.

C'è un esempio, in cui il caso delle variabili membro è il risultato finale del refactoring. È quando si dispone di un metodo complesso e lungo, ma il refactoring in singole chiamate di metodo introdurrebbe tonnellate di parametri del metodo passati in giro. In questo caso, converti l'intero metodo in una singola classe e trasforma le variabili del metodo in membri di una classe. Quindi, puoi facilmente dividere l'unico metodo enorme in più piccoli che sono tutti contenuti nella classe e che non devono passare attorno a tutte le variabili, perché sono contenute nella stessa classe.

    
risposta data 03.12.2014 - 19:34
fonte

Leggi altre domande sui tag