Passa al singolo oggetto impostazioni rispetto a più metodi setter?

3

Lavorare con C ++. Supponiamo che abbia un BoxFilter di classe. La classe viene utilizzata per filtrare le caselle che hanno proprietà come altezza, larghezza, profondità, peso, ecc. Il filtro potrebbe avere qualcosa come MaxWidth in modo che le scatole con una larghezza maggiore di MaxWidth non passino il filtro. L'utilizzo sarebbe qualcosa come (pseudocodice in stile C #):

IBoxFilter filter = new BoxFilter();

foreach(Box box in boxes)
{
  if(filter.PassesFilter(box))
  {
     // do something  
  }
}

(mi dispiace per l'esempio in C # ma penso che sia più facile da capire)

Nell'impostare l'oggetto filtro ho bisogno di configurare le impostazioni del filtro. Il filtro ha attualmente 7 proprietà anche se potrebbe ottenere più nel tempo. Sto discutendo se la classe filtro debba avere setter multipli come SetWidthMax (), SetWidthMinimum (), SetHeightMax (), SetHeightMinimum (), etc o dovrei creare un oggetto / struct BoxFilterSettings e quindi avere un singolo metodo sul filtro box classe chiamata SetSettings (Impostazioni BoxFilterSettings)?

    
posta User 08.09.2011 - 02:07
fonte

7 risposte

5

Personalmente preferisco le API con entrambi. A volte voglio solo impostare un criterio, quindi costruire un oggetto settings è un dolore. Altre volte, voglio riutilizzare ripetutamente le stesse impostazioni, quindi impostarle singolarmente diventa un problema e creerò il mio oggetto wrapper per le impostazioni se non ne viene fornito uno.

    
risposta data 08.09.2011 - 02:44
fonte
3

Vorrei accendere il problema.

Alcune persone vogliono filtrare in larghezza, altre in altezza, altre vogliono solo forme quadrate o rettangoli che hanno approssimativamente un aspetto 16: 9 ...

non puoi possibilmente proporre tutte le alternative.

Quindi è più facile proporre un'interfaccia:

class IFilter: boost::noncopyable {
public:
  virtual bool passes(Box const& box) const = 0; // const or not ?
  virtual ~IFilter() {}
};

E forse imposta alcuni filtri semplici:

class MaxWidthFilter: public IFilter {
public:
  MaxWidthFilter(size_t max, IFilter const* next = 0): _max(max), _next(next) {}

  virtual bool passes(Box const& box) const {
    if (box.getWidth() > _max) { return false; }
    return _next ? _next->passes(box) : true;
  }

private:
  size_t _max;
  IFilter const* _next;
};

Nota: potresti voler eseguire la clonazione completa / di proprietà qui.

I vantaggi:

  • Extensible
  • L'ordine dei test è configurabile
  • Può fare più del filtraggio (es: statistiche sui test, ...)

Svantaggi:

  • Un po 'più complicato per cominciare (anche se hai già 7 proprietà ...)
  • Probabilmente un po 'più lentamente, anche se la possibilità di riordinare i test a seconda della collezione da filtrare potrebbe pesare su questo

Nota: potresti riconoscere un decoratore qui, o forse una catena di responsabilità.

    
risposta data 08.09.2011 - 20:34
fonte
2

Se stai usando C ++ e ti aspetti che le opzioni siano conosciute quando crei l'oggetto (cioè non stai semplicemente impostandole su un valore specifico e quindi modificandole in base all'input dell'utente), allora pratica comune è passare i dati attraverso più parametri o un singolo oggetto init nella classe costruttore in modo che tu possa approfittare di elenco di inizializzazione .

struct BoxParams
{
    int widthMax;
    int widthMin;
};

class BoxFilter(BoxParams* params)
: mWidthMax(params->widthMax)
, mWidthMin(params->widthMin)
{
}

O qualcosa del genere ..

Personalmente, preferisco usare gli oggetti di inizializzazione in quanto è quasi garantito che l'API non cambi (beh, almeno per l'inizializzazione), quindi non devi preoccuparti di problemi come l'ordine dei parametri quando aggiorni la tua struttura di inizializzazione.

    
risposta data 08.09.2011 - 02:54
fonte
1

The filter currently has 7 properties although it could get more over time. I'm debating whether the filter class should have multiple setters {snip} or should I create a BoxFilterSettings object/struct

Penso che finirai per fare entrambe le cose. Sulla base delle esperienze (presso il mio precedente datore di lavoro) con alcune applicazioni che sono state vendute per oltre 15 anni, aggiungerai lentamente sempre più proprietà. Questo è normale . Ma un costruttore con 20 parametri non lo è. Né ha 20 diversi costruttori, tutti che variano di 1 impostazione.

Che cosa abbiamo finito per fare:

  • Abbiamo lasciato i setter / getter. Aggiungendo altro con il passare degli anni.

  • Abbiamo creato un grande oggetto impostazioni. Questo oggetto delle impostazioni verrebbe passato al costruttore (il numero di costruttori è stato ridotto a 2: uno con le impostazioni, uno con nessuno). Abbiamo anche un setter di impostazioni separato che ha preso questo oggetto impostazioni.

risposta data 08.09.2011 - 18:17
fonte
1

Ci sono grossi problemi con la complessità nell'esempio sopra:

  1. La struttura effettiva del problema è l'array 2d (di booleani) in cui una dimensione è riquadri e un'altra dimensione è le proprietà. Questa struttura proviene praticamente da Visual Basic dal 1980.
  2. Poi ci sono espressioni boolearie arbitrarie che manipolano questi booleani dall'array 2d

Queste due caratteristiche insieme lo rendono estremamente complesso. Ad esempio, il test del funzionamento del codice richiede l'attraversamento di tutti gli elementi dell'array 2d (e la valutazione dell'espressione booleana in ogni elemento dell'array 2d) e quindi un'ulteriore valutazione delle espressioni booleane che collegano le righe e le colonne dell'array 2d. È come aggiungere il foglio di calcolo Excel al tuo programma e poi farlo calcolare alcuni valori dai dati; salvo che è necessario aggiungere manualmente i setter / getter e manipolare i dati è molto oneroso. La complessità è ancora lì.

Quindi la mia raccomandazione è di cercare di renderlo più semplice in qualche modo - ridurre righe / colonne è un inizio, ma cerca di eliminare la struttura di array 2d. In modo che non è necessario implementare l'intero excel per questo. Excel utilizza anche questo tipo di array 2d in cui ogni nodo può contenere dati tipizzati in modo diverso. Queste convenzioni provenienti da C # sembrano avere questo tipo di complessità nascosta in esse.

Ecco un codice c ++ che è praticamente equivalente al tuo codice sopra e ti permette di vedere la struttura attuale dell'array 2d:

template<class T> class Array1d { virtual T Map(int x) const=0; };
class Array2d { virtual bool Map(int x, int y) const=0; };
class Array2dImpl : public Array2d { 
     Array2dImpl(Array1d<Box> &b) : b(b) { } 
     bool Map(int x, int y) const 
        { if (y==0) { return b.Map(x).width < maxwidth; }
           if (y==1) { return b.Map(x).height < maxheight; }
         ...  }
   Array1d<Box> &b;
   int maxwidth;
   int maxheight;
 };
 class BooleanExpression : public Array1d<bool>
 {
 public:
     BooleanExpression(Array2d &a) : a(a) { }
     bool Map(int x) const { return a.Map(x,0) && a.Map(x,1) && a.Map(x,2) && a.Map(x,3); }
 private:
     Array2d &a;
 };

Come il tuo esempio sopra, questo codice può essere usato con una semplice porzione di codice:

int main() {
   Array1d<Box> boxes;
   Array2dImpl array(boxes);
   BooleanExpression e(array);
   for(int i=0;i<e.size();i++) 
      {
       if (e.Map(i)) { /* do something */ }
      }
  }

Ora torniamo alla tua domanda su come impostare la variabile maxwidth in questo esempio. Potrebbe sembrare che richieda setter separato per ogni proprietà. Ma non è questo il caso, in realtà, il modo migliore è aggiungere una funzione come questa:

void SetElement(int x, int y, bool b);

Questo appartiene naturalmente all'array 2d che è già incluso nel tuo esempio. Il vero problema con questo esempio è che ci sono 2 ^ (x * y) diversi stati in cui può essere inserito il sistema. Questo deriva da x * y chiamate a SetElement. Questo tipo di esplosione dello stato è completamente pazzesco per un sistema del genere e questo potrebbe infrangere il tuo software molto male.

Anche se assumiamo che tu voglia generare x * y booleani usando il codice sopra fornendo semplicemente le dimensioni, il trucco di come il tuo esempio ha fatto la malvagia complessità è molto nascosto. Passare la confezione dalla funzione foreach a PassesFilter () sembra molto semplice, ma causa tutto questo problema, riconfigurando tutti i booleani nella singola riga dell'eccel. Correlato è anche lo spazio di stato coinvolto nelle coordinate delle caselle. Questi sono anche problemi peggiori.

(e no, nascondere l'array 2d usando il codice C # non lo rende più facile da capire,)

(In realtà, è molto malvagio pubblicare questo codice per gli altri programmatori da leggere, spero tu non sappia che l'array 2d è lì e hai ancora la possibilità di riparare in qualche modo il tuo software ...)

    
risposta data 08.09.2011 - 22:20
fonte
0

La mia esperienza con jQuery mi dice che passare un oggetto settings è molto più semplice che avere molti metodi setter. Tuttavia, jQuery può farlo, perché supporta la notazione letterale dell'oggetto. In altre parole, puoi semplicemente creare un oggetto al volo con il codice minimo e passarlo a una funzione.

C # non ha potuto farlo finché non ha introdotto il concetto di inizializzatore di oggetti insieme al concetto di tipi anonimi .

Non so sulla sintassi C ++. Ma se dovessi prima creare un oggetto strongmente tipizzato e dichiarare i parametri per ogni impostazione, quindi creare un'istanza di quella classe e quindi assegnare valori a ciascuna proprietà su una nuova riga, per creare un oggetto impostazioni, penso che attenersi al singolo il metodo sarebbe più fine.

    
risposta data 09.09.2011 - 06:21
fonte
-2

Due parole: modello generatore. E inoltre, prego.

Ad esempio:

class BoxFilter {
    friend BoxFilterBuilder;
    private int height;
    private int width;
};
class BoxFilterBuilder {
    int height_;
    int width_;
    public:
    BoxFilterBuilder& height(int x) {this.height_ = x; return *this;}
    BoxFilterBuilder& width(int x) {this.width_ = x; return *this;}
    BoxFilter build() {
        BoxFilter filter;
        filter.height = height_;
        filter.width = width_;
        return filter;
    }
};

L'utilizzo è simile a questo:

BoxFilter filter = BoxFilterBuilder().height(12).width(1).build();

Come puoi vedere la sintassi è chiara e rimane tale indipendentemente dal numero di parametri di costruzione. E il filtro box è sempre costruito in uno stato corretto (puoi garantirlo con alcune validazioni nel tuo metodo build (). Inoltre puoi forzare la creazione tramite il builder rendendo privato il costruttore di BoxFilter.

    
risposta data 08.09.2011 - 02:19
fonte

Leggi altre domande sui tag