Pregunta

A menos que esté completamente equivocada, el patrón de captador / definidor es un patrón común que se utiliza para dos cosas:

  1. Para hacer una variable privada para que pueda ser utilizado, pero nunca modificada, sólo proporciona un método getVariable (o, más raramente, sólo se puede modificar, por única proporcionando un método setVariable).
  2. Para asegurarse de que, en el futuro, si le sucede que tiene un problema en que una buena solución sería simplemente para tratar la variable antes de que entre dentro y / o fuera de la clase, se puede tratar a la variable mediante el uso de una implementación real de los métodos getter y setter en lugar de la simple devolución o el establecimiento de los valores. De esta manera, el cambio no se propaga al resto del código.

Pregunta # 1: ¿Me estoy perdiendo toda utilización de métodos de acceso o son cualquiera de mis suposiciones incorrectas? No estoy seguro de si estoy en lo correcto en ellos.

Pregunta # 2: ¿Hay algún tipo de bondad plantilla que me pueda evitar tener que escribir los descriptores de acceso para mis variables miembro? No he encontrado ninguna.

Pregunta # 3: ¿La plantilla de clase siguiente ser una buena manera de poner en práctica un captador sin tener que escribir realmente el 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;
}

Parece justo para mí, y que incluso podría volver a desarrollar la lógica get mediante el uso de algún otro engaño plantilla de especialización parcial. Lo mismo se puede aplicar a algún tipo de plantillas de clase Setter o incluso GetterSetter.

¿Cuáles son sus pensamientos?

¿Fue útil?

Solución

Mientras que la solución es clara desde el punto de vista de la implementación, arquitectónicamente, es sólo la mitad del camino. El punto de la pauta de captador / definidor es dar a los clas control sobre los datos de TI de y para disminuir el acoplamiento (es decir, otra clase sabiendo cómo datos se almacenan). Esta solución consigue la primera, pero no es el último.

De hecho la otra clase ahora tiene que saber dos cosas - el nombre de la variable y el método en el getter (es decir .get()) en lugar de uno - por ejemplo, getWidth(). Esto causa un incremento de acoplamiento.

Una vez dicho todo esto, este es pelos arquitectónicos proverbiales división. No importa todo lo que mucho al final del día.

Editar OK, ahora para mierdas y risitas, que aquí hay una versión del captador mediante operadores, por lo que no tiene que hacer .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
}

editar fijo operador de asignación hardcoded. Esto debería funcionar razonablemente bien si el tipo en sí tiene un operador de asignación. Por defecto estructuras tienen los así que para los simples que deben trabajar fuera de la caja.

Para las clases más complejas que se necesitan para poner en práctica un operador de asignación, que es justo lo suficiente. Con RVO y copia en optimizaciones de escritura , esto debe ser razonablemente eficiente en tiempo de ejecución.

Otros consejos

Fwiw aquí están mis comentarios sobre sus preguntas:

  1. Típicamente, el punto es que no es la lógica de negocio o de otras restricciones forzadas en la incubadora. También puede haber calculado o las variables virtuales por desacoplamiento de la variable de instancia con métodos de acceso.
  2. No que yo sepa. Proyectos en los que he trabajado han tenido una familia de C macros para acabar con tales métodos
  3. Sí; Creo que es bastante limpio. Sólo me preocupa que no vale la pena, sólo va a confundir a otros desarrolladores (un concepto más que necesitan para caber en la cabeza) y no está ahorrando mucho a lo largo de estampado a cabo tales métodos manualmente.

Desde Igor Zevaka publicó una versión de este, voy a publicar uno que escribí hace mucho tiempo. Esto es un poco diferente - he observado en el momento en que más el uso real de los pares get / set (que en realidad hizo nada) era hacer cumplir el valor de una variable de permanencia dentro de un rango predeterminado. Esto es un poco más amplia, tales como la adición de I / operadores O, donde extractor todavía hace cumplir el rango definido. También tiene un poco de código de prueba / ejercicio para mostrar la idea general de lo que hace y cómo lo hace:

#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. También puede utilizar un método getter o setter tipo para obtener o establecer los valores computables, tanto la forma en que se utilizan las propiedades en otros lenguajes como C #

  2. No puedo pensar en forma razonable para abstraer la obtención y el establecimiento de un número desconocido de valores / propiedades.

  3. No soy lo suficientemente familiarizado con el estándar de C ++ buey para comentar.

Esto puede ser excesiva en este caso, sino que debe salir el lenguaje de abogado / cliente para el uso juicioso amistad. Antes de encontrar este idioma, evité la amistad por completo.

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

Y ahora la pregunta, y lo que si usted necesita un setter también.

No sé ustedes, pero que tienden a tener (más o menos) dos tipos de clases:

  • clase para la lógica
  • gotas

Las burbujas son colecciones simplemente sueltos de todas las propiedades de un objeto de negocio. Por ejemplo, un Person tendrá un surname, firstname, varias direcciones, varias profesiones ... así Person puede no tener lógica.

Para las manchas, que tienden a utilizar el atributo privado canónica + + colocador captador, ya que abstrae la implementación real del cliente.

Sin embargo, a pesar de su plantilla (y su evolución por Igor Zeveka) son muy agradables, no abordan el problema de ajuste y no abordan compatibilidad binaria problemas.

supongo que probablemente recurrir a las macros ...

Algo así como:

// 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; }

que se utilizaría como:

// 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 poco de magia preprocesador que iba a funcionar bastante bien!

#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)

Bueno, no es un tipo seguro, y todo, pero:

  • se trata de un conjunto razonable de macro Creo
  • que es fácil de usar, el usuario sólo tenga que preocuparse de 2 macros después de todo, a pesar de las plantillas como los errores podrían obtener peluda
  • uso de boost.call_traits para la eficiencia (const y / valor de la opción)
  • hay más funcionalidad allí: captador / definidor dúo

  • es, por desgracia, un conjunto de macros ... y no se quejará si alguna vez

  • que causa estragos en los descriptores de acceso (public, protected, privado), así que es mejor no Intersped a lo largo de la clase

Aquí está el ejemplo canónico a continuación:

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:

};

Esta resolver el problema equivocado. En una aplicación bien diseñado, captadores y definidores deben ser raro , no automatizado. Una clase significativa proporciona algún tipo de abstracción . No es simplemente una colección de miembros, que modela un concepto que es algo más que la suma de sus variables miembro. Y por lo general ni siquiera tiene sentido para exponer a los miembros individuales.

Una clase debe exponer las operaciones que tienen sentido en el concepto de que los modelos. La mayoría de las variables miembro están ahí para mantener esta abstracción, para almacenar el estado que necesita. Pero por lo general no se debe acceder directamente. Es por eso que es un miembro privado de la clase en el primer lugar .

En lugar de encontrar maneras más fáciles de escribir car.getFrontLeftWheel(), preguntarse por qué el usuario de la clase podría necesitar la rueda delantera izquierda en el primer lugar. ¿En general, manipular esa rueda directamente al conducir? El coche se supone que debe hacerse cargo de todo el negocio de la rueda de hilado para usted, ¿verdad?

Aquí es donde creo que #defines siguen siendo útiles.

La versión de la plantilla es complicado y difícil de entender - la versión definir es obvio

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

class Window
{
    Getter(int, height);
}

Estoy seguro de que tengo la sintaxis un poco mal - pero usted consigue el punto

.

Si había un conjunto bien conocido de plantillas en, por ejemplo, aumentar luego usaría. Pero no me gustaría escribir mi propia.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top