Pergunta

Se não estou completamente enganado, o padrão getter / setter é um padrão comum usada para duas coisas:

  1. Para fazer uma variável privada de modo que ele pode ser usado, mas nunca modificado, por apenas fornecer um método getVariable (ou, mais raramente, apenas a modificável, por apenas fornecer um método setVariable).
  2. Para certificar-se de que, no futuro, se acontecer de você ter um problema para o qual uma boa solução seria simplesmente para tratar a variável antes que ele entra e / ou fora da classe, você pode tratar a variável usando uma implementação real sobre os métodos get e set em vez de simplesmente retornar ou definir os valores. Dessa forma, a alteração não propagar para o resto do código.

Pergunta # 1: Eu estou faltando qualquer uso de assessores ou algum de minhas suposições incorretas? Eu não tenho certeza se eu estou correto sobre aqueles.

Pergunta # 2: Há algum tipo de bondade modelo que pode me impedir de ter que escrever os acessores para meus variáveis ??de membro? I não encontrou qualquer.

Pergunta # 3:? Será que o seguinte modelo de classe ser uma boa maneira de implementar um getter sem ter que escrever o 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 mim, e eu poderia até reimplementar a lógica get usando algum outro artifício parcial modelo de especialização. O mesmo pode ser aplicado a algum tipo de modelos de classe Setter ou mesmo GetterSetter.

Quais são seus pensamentos?

Foi útil?

Solução

Enquanto a solução é puro do ponto de vista da implementação, architectually, é apenas metade do caminho. O ponto do padrão Getter / Setter é dar as clas controle sobre ele de dados e diminuir o acoplamento (ou seja, outra classe sabendo como dados são armazenados). Esta solução alcança o primeiro, mas não é o último.

Na verdade, a outra classe agora tem que saber duas coisas - o nome da variável e o método no getter (ou seja .get()) em vez de um - por exemplo, getWidth(). Este causas maior acoplamento.

Dito tudo isso, este é dividir cabelos arquitectónicas proverbiais. Não importa tanto assim, no final do dia.

Editar OK, agora para merdas e risadinhas, aqui está uma versão do getter usando operadores, para que você não tem que fazer .value ou .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 Fixed codificado operador de atribuição. Isso deve funcionar razoavelmente bem se o tipo em si tem um operador de atribuição. Por padrão estruturas têm os assim para os simples que deve funcionar fora da caixa.

Para as classes mais complexas que você precisa para implementar um operador de atribuição que é suficiente justo. Com RVO e otimizações cópia na escrita , este deve ser razoavelmente eficiente em tempo de execução.

Outras dicas

FWIW aqui estão as minhas opiniões sobre suas perguntas:

  1. Normalmente, o ponto é que não há lógica de negócios ou outras restrições impostas no setter. Você também pode ter calculado ou variáveis ??virtuais, dissociando a variável de instância com métodos de acesso.
  2. Não que eu saiba. Projectos em que já trabalhei tiveram uma família de macros C para acabar com tais métodos
  3. Sim; Eu acho que é muito arrumado. Eu só preocupe não é vale a pena, ele só vai confundir os outros desenvolvedores (mais um conceito de que precisam para se encaixar em sua cabeça) e não está a poupar muito ao longo carimbar a tais métodos manualmente.

Desde Igor Zevaka postou uma versão deste, vou postar que eu escrevi há muito tempo. Isso é um pouco diferente - eu observado no momento em que mais uso real do GET / pares de conjunto (que realmente fez alguma coisa) era para fazer cumprir o valor de uma estada variável dentro de um intervalo pré-determinado. Isto é um pouco mais extensas, tais como a adição de operadores de E / S, onde extractor ainda reforça a gama definida. Ele também tem um pouco de código de teste / exercício para mostrar a idéia geral do que ele faz e como ele faz isso:

#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. Você também pode usar um método getter ou tipo setter para obter ou definir valores computáveis, da mesma forma propriedades são usadas em outras linguagens como C #

  2. Eu não posso pensar de forma razoável para abstrair a obtenção e definição de um número desconhecido de valores / propriedades.

  3. Eu não sou suficientemente familiarizado com o C ++ padrão boi para comentários.

Este pode ser um exagero neste caso, mas você deve verificar se o idioma advogado / cliente para o uso amizade criteriosa. Antes de encontrar este idioma, eu evitava amizade por completo.

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

E agora a pergunta, e se você precisa de um setter também.

Eu não sei sobre você, mas eu tendem a ter (mais ou menos) dois tipos de classes:

  • classe para a lógica
  • blobs

As bolhas são coleções apenas soltas de todas as propriedades de um objeto de negócios. Por exemplo, um Person terá um surname, firstname, vários endereços, várias profissões ... então Person pode não ter lógica.

Para as bolhas, que tendem a usar o atributo privado canônica + getter + setter, uma vez que abstrai a implementação real do cliente.

No entanto, embora o seu modelo (e sua evolução por Igor Zeveka) são realmente bons, eles não abordam o problema configuração e eles não endereço binário compatibilidade questões.

Eu acho que eu provavelmente iria recorrer a macros ...

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

Qual seria usado 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

Com um pouco de magia pré-processador que iria funcionar muito bem!

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

Ok, não digite seguro, e tudo, mas:

  • é um conjunto razoável de macro Acho
  • é fácil de usar, o utilizador só tem de se preocupar com 2 macros Afinal, embora, como modelos os erros poderiam se complicar
  • uso de boost.call_traits para a eficiência (const & / valor escolha)
  • há mais funcionalidade lá: getter / setter duo

  • é, infelizmente, um conjunto de macros ... e não vai reclamar se você nunca

  • que causa estragos sobre os acessores (público, protegido, privado), então não é melhor para intersped-lo em toda a classe

Aqui está o exemplo canônico então:

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:

};

Você está resolvendo o problema errado. Em um aplicativo bem projetado, getters e setters deve ser raras , não automatizado. Uma classe significativa fornece algum tipo de abstração . Não é simplesmente uma coleção de membros, ela modela um conceito que é mais do que apenas a soma de suas variáveis ??de membro. E normalmente não faz muito sentido para expor os membros individuais.

A classe deve expor as operações que fazem sentido no conceito de que os modelos. A maioria das variáveis ??de membro estão lá para manter essa abstração, para armazenar o estado que você precisa. Mas normalmente não devem ser acessados ??diretamente. É por isso que é um membro particular da classe em primeiro lugar .

Ao invés de encontrar maneiras mais fáceis de escrever car.getFrontLeftWheel(), pergunte-se por que o usuário da classe poderia precisar a roda dianteira esquerda, em primeiro lugar. Você costuma manipular que roda diretamente ao dirigir? O carro é suposto para cuidar de todos os negócios da roda de fiação para você, não é?

Este é onde eu acho que #defines ainda são úteis.

A versão do modelo é complicado e difícil de entender - a definir versão é óbvio

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

class Window
{
    Getter(int, height);
}

Tenho certeza de que tem a sintaxe ligeiramente errado - mas você começa o ponto

.

Se houvesse um conjunto bem conhecido de modelos em, digamos, impulso, então eu iria usá-los. Mas eu não iria escrever o meu próprio.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top