Domanda

A meno che io sono completamente in errore, l'/ modello setter getter è un modello comune utilizzato per due cose:

  1. Per rendere una variabile privata in modo che possa essere utilizzato, ma mai modificato, fornendo solo un metodo getVariable (o, più raramente, solo modificabile, fornendo solo un metodo setVariable).
  2. Per fare in modo che, in futuro, se vi capita di avere un problema a cui una buona soluzione sarebbe semplicemente per trattare la variabile prima che va dentro e / o fuori della classe, è possibile trattare la variabile utilizzando un'implementazione effettiva sulle getter e setter invece di restituire semplicemente o modificando i valori. In questo modo, il cambiamento non si propaga al resto del codice.

Domanda # 1: Mi sto perdendo qualsiasi uso delle funzioni di accesso o sono una delle mie ipotesi errata? Non sono sicuro se non sbaglio su quelli.

Domanda # 2: Ci sono alcun tipo di modello di bontà che mi può evitare di dover scrivere le funzioni di accesso per i miei variabili membro? Non ho trovato alcuna.

Domanda # 3:? Sarebbe il seguente modello di classe essere un buon modo di attuare un getter senza dover scrivere in realtà l'accesor

template <class T>
struct TemplateParameterIndirection // This hack works for MinGW's GCC 4.4.1, dunno others
{
    typedef T Type;
};

template <typename T,class Owner>
class Getter
{
public:
    friend class TemplateParameterIndirection<Owner>::Type; // Befriends template parameter

    template <typename ... Args>
    Getter(Args args) : value(args ...) {} // Uses C++0x

    T get() { return value; }

protected:
    T value;
};

class Window
{
public:
    Getter<uint32_t,Window> width;
    Getter<uint32_t,Window> height;

    void resize(uint32_t width,uint32_t height)
    {
        // do actual window resizing logic

        width.value = width; // access permitted: Getter befriends Window
        height.value = height; // same here
    }
};

void someExternalFunction()
{
    Window win;

    win.resize(640,480); // Ok: public method

    // This works: Getter::get() is public
    std::cout << "Current window size: " << win.width.get() << 'x' << win.height.get() << ".\n";

    // This doesn't work: Getter::value is private
    win.width.value = 640;
    win.height.value = 480;
}

Sembra giusto per me, e ho potuto persino reimplementare la logica get utilizzando qualche altro parziale inganno modello di specializzazione. Lo stesso può essere applicato a una sorta di template di classe Setter o addirittura GetterSetter.

Quali sono i tuoi pensieri?

È stato utile?

Soluzione

Mentre la soluzione è pulito dal punto di vista dell'attuazione, architettonicamente, è solo a metà strada. Il punto del / modello Setter Getter è quello di dare il controllo delle clas sopra esso è di dati e per ridurre l'accoppiamento (cioè altra classe sapendo come i dati vengono memorizzati). Questa soluzione realizza la prima ma non abbastanza quest'ultimo.

Infatti l'altra classe ha ora sapere due cose - il nome della variabile e il metodo sul getter (cioè .get()) invece di uno - es getWidth(). Ciò causa un aumento di accoppiamento.

Detto questo, si tratta di scissione peli architettoniche proverbiali. Non importa più di tanto, alla fine della giornata.

Modifica OK, ora merda e risatine, qui è una versione del getter utilizzando gli operatori, in modo da non dovete fare .value o .get()

template <class T>
struct TemplateParameterIndirection // This hack works for MinGW's GCC 4.4.1, dunno others
{
    typedef T Type;
};

template <typename T,class Owner>
class Getter
{
public:
    friend TemplateParameterIndirection<Owner>::Type; // Befriends template parameter

    operator T()
    {
        return value;
    }

protected:
    T value;

    T& operator=( T other )
    {
       value = other;
       return value;  
    }


};

class Window
{
public:
    Getter<int,Window> _width;
    Getter<int,Window> _height;

    void resize(int width,int height)
    {
        // do actual window resizing logic
        _width = width; //using the operator
        _height = height; //using the operator
    }
};

void someExternalFunction()
{
    Window win;

    win.resize(640,480); // Ok: public method
    int w2 = win._width; //using the operator
    //win._height = 480; //KABOOM
}

Modifica fissi operatore di assegnazione hardcoded. Questo dovrebbe funzionare ragionevolmente bene se il tipo di per sé ha un operatore di assegnazione. Per impostazione predefinita, le strutture hanno quelli così per quelle semplici che dovrebbe funzionare out of the box.

Per le classi più complesse sarà necessario implementare un operatore di assegnazione che è abbastanza giusto. Con RVO e copia su ottimizzazioni write , questo dovrebbe essere ragionevolmente efficiente in fase di esecuzione.

Altri suggerimenti

FWIW qui sono le mie opinioni sulle vostre domande:

  1. In genere il punto è che non c'è logica di business o di altri vincoli imposti nel setter. È anche possibile avere calcolato o variabili virtuali disaccoppiando la variabile di istanza con metodi di accesso.
  2. Non che io sappia. I progetti cui ho lavorato hanno avuto una famiglia di C macro a debellare tali metodi
  3. Sì; Penso che sia abbastanza carino. Mi preoccupo che non vale la pena, sarà solo confondere altri sviluppatori (un altro concetto di cui hanno bisogno per adattarsi nella loro testa) e non sta risparmiando molto nel corso stamping out tali metodi manualmente.

Da Igor Zevaka ha registrato una versione di questa, vi posterò uno che ho scritto tanto tempo fa. Questo è leggermente diverso - ho osservato nel momento in cui più reale utilizzo di coppie get / set (che in realtà ha fatto nulla) è stato quello di far rispettare il valore di una variabile di permanenza all'interno di un intervallo predeterminato. Questo è un po 'più ampia, come l'aggiunta di I / O operatori, dove estrattore impone comunque l'intervallo definito. Essa ha anche un po 'di codice di test / esercizio per mostrare l'idea generale di ciò che fa e come lo fa:

#include <exception>
#include <iostream>
#include <functional>

template <class T, class less=std::less<T> >
class bounded {
    const T lower_, upper_;
    T val_;

    bool check(T const &value) {
        return less()(value, lower_) || less()(upper_, value);
    }

    void assign(T const &value) {
        if (check(value))
            throw std::domain_error("Out of Range");
        val_ = value;
    }

public:
    bounded(T const &lower, T const &upper) 
        : lower_(lower), upper_(upper) {}

    bounded(bounded const &init) 
        : lower_(init.lower), upper_(init.upper)
    { 
        assign(init); 
    }

    bounded &operator=(T const &v) { assign(v);  return *this; }

    operator T() const { return val_; }

    friend std::istream &operator>>(std::istream &is, bounded &b) {
        T temp;
        is >> temp;

        if (b.check(temp))
            is.setstate(std::ios::failbit);
        else
            b.val_ = temp;
        return is;
    }
};


#ifdef TEST

#include <iostream>
#include <sstream>

int main() {
    bounded<int> x(0, 512);

    try {
        x = 21;
        std::cout << x << std::endl;

        x = 1024;
        std::cout << x << std::endl;
    }

    catch(std::domain_error &e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    std::stringstream input("1 2048");
    while (input>>x)
        std::cout << x << std::endl; 

    return 0;
}

#endif
  1. È anche possibile utilizzare un metodo getter o setter tipo per ottenere o impostare i valori computabili, tanto le proprietà modo in cui vengono utilizzati in altri linguaggi come C #

  2. Non riesco a pensare a modo ragionevole di astrarre il recupero o l'impostazione di un numero imprecisato di valori / proprietà.

  3. io non sono abbastanza familiarità con il C ++ standard di bue per commentare.

Questo può essere eccessivo in questo caso, ma si dovrebbe verificare l'idioma avvocato / cliente per un utilizzo giudizioso amicizia. Prima di trovare questo idioma, ho evitato l'amicizia del tutto.

http://www.ddj.com/cpp/184402053/

E ora la domanda, e che cosa se avete bisogno di un setter pure.

Non so voi, ma io tendono ad avere (o meno) di due tipi di classi:

  • classe per la logica
  • blob

Le macchie sono raccolte solo allentate di tutte le proprietà di un oggetto business. Per esempio, un Person avrà un surname, firstname, diversi indirizzi, diverse professioni ... quindi Person non può avere la logica.

Per le macchie, tendo a utilizzare l'attributo privato canonica + getter + setter, dal momento che astrae l'effettiva attuazione da parte del cliente.

Tuttavia, anche se il modello (e la sua evoluzione da Igor Zeveka) sono veramente bello, non affrontano il problema di impostazione e non affrontano compatibilità binaria problemi.

Credo che avrei probabilmente ricorrere a macro ...

Qualcosa di simile:

// Interface
// Not how DEFINE does not repeat the type ;)
#define DECLARE_VALUE(Object, Type, Name, Seq) **Black Magic Here**
#define DEFINE_VALUE(Object, Name, Seq) ** Black Magic Here**

// Obvious macros
#define DECLARE_VALUER_GETTER(Type, Name, Seq)\
   public: boost::call_traits<Type>::const_reference Name() const

#define DEFINE_VALUE_GETTER(Object, Name)\
   boost::call_traits<Name##_type>::const_reference Object::Name ()const\
   { return m_##Name; }

#define DECLARE_VALUE_SETTER(Object, Type, Name)\
   public: Type& Name();\
   public: Object& Name(boost::call_traits<Type>::param_type i);

#define DEFINE_VALUE_SETTER(Object, Name)\
   Name##_type& Object::Name() { return m_##Name; }\
   Object& Object::Name(boost::call_traits<Name##_type>::param_type i)\
   { m_##Name = i; return *this; }

che sarebbe utilizzato come:

// window.h
DECLARE_VALUE(Window, int, width, (GETTER)(SETTER));

// window.cpp
DEFINE_VALUE(Window, width, (GETTER)); // setter needs a bit of logic

Window& Window::width(int i) // Always seems a waste not to return anything!
{ 
  if (i < 0) throw std::logic_error();
  m_width = i;
  return *this;
} // Window::width

Con un po 'di magia preprocessore che avrebbe funzionato abbastanza bene!

#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/tuple/rem.hpp>

#define DECLARE_VALUE_ITER(r, data, elem)\
  DECLARE_VALUE_##elem ( BOOST_PP_TUPLE_REM(3)(data) )

#define DEFINE_VALUE_ITER(r, data, elem)\
  DEFINE_VALUE_##elem ( BOOST_PP_TUPLE_REM(2)(data) )

#define DECLARE_VALUE(Object, Type, Name, Seq)\
   public: typedef Type Name##_type;\
   private: Type m_##Name;\
   BOOST_PP_SEQ_FOREACH(DECLARE_VALUE_ITER, (Object, Type, Name), Seq)

#define DEFINE_VALUE(Object, Name, Seq)\
   BOOST_PP_SEQ_FOREACH(DEFINE_VALUE_ITER, (Object, Name), Seq)

D'accordo, non di tipo sicuro, e tutti, ma:

  • è un insieme ragionevole di macro penso
  • è facile da usare, l'utente sempre e solo deve preoccuparsi di 2 macro, dopo tutto, anche se i modelli di come gli errori potrebbero ottenere peloso
  • uso di boost.call_traits per l'efficienza (const & / scelta di valore)
  • non c'è più funzionalità c'è: getter / setter duo

  • è, purtroppo, un insieme di macro ... e non si lamenterà se mai

  • seminano il terrore sulle funzioni di accesso (public, protected, private), quindi è meglio non Intersped per tutta la classe

Ecco l'esempio canonico poi:

class Window
{
  // Best get done with it
  DECLARE_VALUE(Window, int, width, (GETTER));
  DECLARE_VALUE(Window, int, height, (GETTER));

// don't know which is the current access level, so better define it
public:

};

Si sta risolvendo il problema sbagliato. In un'applicazione ben progettata, getter e setter dovrebbe essere rare , non automatizzato. Una classe significativa fornisce una sorta di astrazione . Non è semplicemente una raccolta di membri, modella un concetto che non è solo la somma delle sue variabili membro. E in genere non ha nemmeno senso per esporre i singoli membri.

Una classe dovrebbe esporre le operazioni che hanno senso sul concetto che i modelli. La maggior parte delle variabili membro sono lì per mantenere questa astrazione, per memorizzare lo stato di cui avete bisogno. Ma in genere non è possibile accedere direttamente. Questo è il motivo per cui è un membro privato della classe, in primo luogo .

Invece di trovare il modo più facile scrivere car.getFrontLeftWheel(), chiedetevi perché l'utente della classe avrebbe mai bisogno la ruota anteriore sinistra, in primo luogo. Lei di solito manipolare che ruota direttamente quando si guida? La vettura dovrebbe prendersi cura di tutti i business ruota gira per te, non è vero?

Questo è dove penso #defines sono ancora utili.

La versione del modello è complicato e difficile da capire - la versione definire è ovvio

#define Getter(t, n)\
     t n;\
     t get_##n() { return n; }

class Window
{
    Getter(int, height);
}

Sono sicuro di avere la sintassi leggermente sbagliato - ma si ottiene il punto

.

Se ci fosse un insieme noto di modelli in, per esempio, aumentare allora io li uso. Ma non vorrei scrivere il mio.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top