2013-01-25 14 views
6

Si consideri il seguente classe:Utilizzo dell'overloader value e dell'overloading() per semplificare il design getter/setter: una pratica pericolosa?

class MyClass1 
{ 
    public: 
     double x() const {return _x;} // getter 
     double y() const {return _y;} // getter 
     double z() const {return _x*_y;} // getter 
     void x(const double var) {_x = var;} // setter 
     void y(const double var) {_y = var;} // setter 
     void z(const double var) {_x = var; _y = 1;} // setter 
    protected: 
     double _x; 
     double _y; 
}; 

Come il contenuto effettivo di MyClass1 è un dettaglio di implementazione, i getter e setter forniscono un modo unificato per ottenere e impostare il contenuto di classe, anche se sono interdipendenti (qui _z non esiste internamente ma per l'utente, z è una variabile come x e).

Ora per evitare di dover scrivere getter/setter per x e y, si può usare un wrapper come questo:

template <typename Type> 
class Wrapper 
{ 
    public: 
     constexpr Wrapper(const Type& value) {_value = value;} 
     constexpr Type& operator()() {return _value;} 
     constexpr const Type& operator()() const {return _value;} 
     constexpr void operator()(const Type& value) {_value = value;} 
    protected: 
     _value; 
}; 

E ora la classe originale diventa:

class MyClass2 
{ 
    public: 
     Wrapper<double> x; 
     Wrapper<double> y; 
     double z() const {return x*y;} // getter 
     void z(const double var) {x = var; y = 1;} // setter 
}; 

E ' una pratica pericolosa o una buona soluzione per evitare di dover scrivere getter/setter?

Nota: qui MyClass1 e MyClass2 sono solo esempi. La mia domanda è molto "generale": è pericoloso sostituire getter/setter di classi con il Wrapper proposto quando il getter/setter restituisce/imposta un valore interno.

+2

Suppongo che avresti bisogno di implementare un qualche tipo di modello di comportamento per fare effettivamente qualcosa (convalidare, notificare, ecc.) sugli accessors. In ogni caso, avrebbero sconfitto lo scopo di averli. Non pensi? imho – imreal

+1

perché la tua classe ha bisogno dell'interfaccia get/set? se queste funzioni non devono mantenere invariante una classe, non ha molto senso inglobarle. – TemplateRex

risposta

1

Come già accennato, lo scopo principale di getter e setter è fornire un modo unificato di accesso e impostazione delle variabili di istanza private.

Dalla tua soluzione di wrapper, se a volte lungo la traccia del ciclo di vita del programma decidi di cambiare un setter, puoi semplicemente gettare via l'involucro e sostituirlo con il getter/setter originale come in MyClass1 - senza dover cambiare tutto altri componenti del codice che lo chiama.

Quindi da quel punto penso che la soluzione sia un modo valido per evitare di digitare poche righe di codice.

1

Non vedo nulla di particolarmente pericoloso lì, ma non sembra guadagnare nulla. C++ fornisce semantica di riferimento per qualsiasi tipo di oggetto, che è incompatibile con una funzione setter. A volte i contenuti reali non sono solo un dettaglio.

si può anche andare nella direzione opposta (in realtà, questo è quello che mi aspettavo di vedere quando ho cliccato questa domanda):

struct virt_z { 
    double x; 
    double y; 
    struct z_wrap { 
     virt_z &parent; 

     operator double() { return parent.x * parent.y; } 
     z_wrap &operator=(double in) { 
      parent.x = in; 
      parent.y = 1; 
      return *this; 
     } 
     z_wrap(virt_z &in) : parent(in) {} 
    } z; 

    virt_z() : z(*this) {} 
    virt_z(virt_z const &o) : x(o.x), y(o.y), z(*this) {} 
}; 

https://ideone.com/qqgj3D

+1

E che dire del tuo povero client che deve leggere questa interfaccia e capire che per impostare z è necessario assegnare a z attraverso un operatore sovraccarico = di una classe wrapper interna. Il semplice stile di funzione getter e setter dell'OP MyClass1 è migliore sia di MyClass2 che della tua soluzione perché è chiaro dalla lettura dell'interfaccia come usarlo. Dato che tutti e tre compili allo stesso codice, sostengo che MyClass1 dovrebbe essere favorito. –

+0

@ user1131467 Cose diverse si adattano a situazioni diverse. Per quanto riguarda MyClass1, qualsiasi funzione che faccia più di "set' z' "non dovrebbe essere chiamata" 'SetZ()' ", e questo è il problema fondamentale con il formalismo getter/setter. Se l'impostazione di qualcosa ha degli effetti collaterali, allora non stai semplicemente impostando uno stato. Allo stesso modo, raramente * davvero * basta cambiare un'interfaccia per aggiungere effetti collaterali all'interno dello stesso oggetto. È solo un mito perpetuato dai libri di testo OO. – Potatoswatter

+0

Perché l'utente deve preoccuparsi dei dettagli di implementazione dell'interfaccia? Dovrebbero semplicemente usarlo naturalmente. – balki

1

Non è pericoloso in quanto tale, è solo più difficile da usare (per l'utente della classe). MyClass2 offusca e ingombra l'interfaccia.

Potrebbe rendere meno noioso scrivere il codice, ma è sufficiente scrivere il codice una sola volta, l'interfaccia verrà riutilizzata più volte. Pertanto il lettore di codice dovrebbe essere favorito.

MyClass1 è superiore alla soluzione MyClass2 e Potatoswatter per questo motivo.

(Anche se ci fosse un modo in C++ per fare questo:

class MyClass3 
{ 
    double x, y, z; 
} 

MyClass3::z::operator=(double) { ... } // property set 
double MyClass3::z::operator() { ... } // property get 

come in C# sarebbe comunque una cattiva idea, credo perché questa sorta di codice "nascosto" porta a cattive assunzioni che possono rallentare il debugging Lo stile della funzione ti fa pensare che potrebbe esserci qualcosa di più complicato di una copia primitiva.)