ti-enxame.com

Convenções para métodos de acessador (getters e setters) em C ++

Várias perguntas sobre métodos acessadores em C++ foram feitas no SO, mas nenhuma foi capaz de satisfazer minha curiosidade sobre o assunto.

Tento evitar acessadores sempre que possível, porque, como Stroustrup e outros programadores famosos, considero uma aula com muitos deles um sinal de mau funcionamento do sistema operacional. Em C++, na maioria dos casos, posso adicionar mais responsabilidade a uma classe ou usar a palavra-chave friend para evitá-las. No entanto, em alguns casos, você realmente precisa de acesso a alunos específicos da classe.

Existem várias possibilidades:

1. Não use acessadores

Podemos apenas tornar públicas as respectivas variáveis ​​de membro. Isso não é permitido em Java, mas parece estar bem com a comunidade C++. No entanto, estou um pouco preocupado com os casos em que uma cópia explícita ou uma referência somente leitura (const) a um objeto devem ser retornadas, isso é exagerado?

2. Use métodos get/set no estilo Java

Não tenho certeza se é de Java, mas quero dizer isso:

int getAmount(); // Returns the amount
void setAmount(int amount); // Sets the amount

. Use os métodos get/set do estilo C do objetivo

Isso é um pouco estranho, mas aparentemente cada vez mais comum:

int amount(); // Returns the amount
void amount(int amount); // Sets the amount

Para que isso funcione, você precisará encontrar um nome diferente para sua variável de membro. Algumas pessoas acrescentam um sublinhado, outras acrescentam "m_". Eu também não gosto.

Qual estilo você usa e por quê?

71
Noarth

Da minha perspectiva, sentado com 4 milhões de linhas de código C++ (e isso é apenas um projeto) do ponto de vista da manutenção, eu diria:

  • Não há problema em não usar getters/setters se os membros forem imutáveis ​​(por exemplo, const) ou simples sem dependências (como uma classe de ponto com os membros X e Y).

  • Se o membro for apenas private, também é possível pular getters/setters. Também conto membros de classes internas pimpl - como private se a unidade .cpp for pequena.

  • Se o membro for public ou protected (protected for tão ruim quanto public) e não -const, não simples ou tiver dependências, use getters/setters.

Como técnico de manutenção, minha principal razão para querer ter getters/setters é porque então eu tenho um lugar para colocar pontos de interrupção/registro/outra coisa.

Prefiro o estilo da alternativa 2., pois é mais pesquisável (um componente-chave na escrita de código de manutenção).

39

2) é a melhor IMO, porque torna suas intenções mais claras. set_amount(10) é mais significativo que amount(10) e, como efeito colateral agradável, permite a um membro chamado amount.

Variáveis ​​públicas é geralmente uma má ideia, porque não há encapsulamento. Suponha que você precise atualizar um cache ou atualizar uma janela quando uma variável for atualizada? Pena que suas variáveis ​​são públicas. Se você tiver um método definido, poderá adicioná-lo lá.

7
AshleysBrain
  1. Eu nunca uso esse estilo. Como isso pode limitar o futuro do design de sua classe, geters ou setters explícitos são igualmente eficientes com bons compiladores.

    Obviamente, na realidade, getters ou setters explícitos embutidos criam a mesma dependência subjacente à implementação da classe. Eles apenas reduzem a dependência semântica. Você ainda precisa recompilar tudo se você os alterar.

  2. Esse é o meu estilo padrão quando uso métodos de acessador.

  3. Esse estilo parece muito "inteligente" para mim. Eu o uso em raras ocasiões, mas apenas nos casos em que realmente quero que o acessador se sinta o máximo possível como uma variável.

Eu acho que há um caso de sacos simples de variáveis ​​com possivelmente um construtor para garantir que todos sejam inicializados com algo sadio. Quando faço isso, simplesmente o faço um struct e deixo tudo público.

7
Omnifarious
  1. Esse é um bom estilo se queremos apenas representar dados pure.

  2. Não gosto :) porque get_/set_ é realmente desnecessário quando podemos sobrecarregá-los em C++.

  3. O STL usa esse estilo, como std::streamString::str e std::ios_base::flags, exceto quando deve ser evitado! quando? Quando o nome do método entra em conflito com o nome de outro tipo, o estilo get_/set_ é usado, como std::string::get_allocator por causa de std::allocator.

6
M. Sadeq H. E.

Em geral, acho que não é uma boa ideia ter muitos getters e setters sendo usados ​​por muitas entidades no sistema. É apenas uma indicação de um design incorreto ou de um encapsulamento errado.

Dito isto, se esse design precisar ser refatorado e o código fonte estiver disponível, eu preferiria usar o padrão Visitor Design. A razão é:

uma. Dá à classe a oportunidade de decidir quem deve permitir o acesso ao seu estado privado

b. Oferece à classe a oportunidade de decidir que acesso permitir a cada uma das entidades interessadas em seu estado privado.

c. Documenta claramente esse acesso externo através de uma interface clara de classe

A ideia básica é:

a) Redesenhar, se possível,

b) Refatorar de modo que

  1. Todo o acesso ao estado da classe é feito através de uma interface bem conhecida individualista

  2. Deve ser possível configurar algum tipo de fazer e não fazer em cada interface, por exemplo todo acesso da entidade externa BOM deve ser permitido, todo acesso da entidade externa RUIM deve ser proibido e entidade externa OK = deve ser permitido obter, mas não definido (por exemplo)

4
Chubsdad
  1. Eu não excluiria os acessadores do uso. Pode ser para algumas estruturas de POD, mas eu as considero uma coisa boa (alguns acessadores também podem ter lógica adicional).

  2. Não importa muito a convenção de nomenclatura, se você for consistente em seu código. Se você estiver usando várias bibliotecas de terceiros, elas podem usar convenções de nomenclatura diferentes de qualquer maneira. Portanto, é uma questão de gosto.

2
Cătălin Pitiș

Eu já vi a idealização de classes em vez de tipos integrais para se referir a dados significativos.

Algo assim abaixo geralmente não está fazendo bom uso das propriedades do C++:

struct particle {
    float mass;
    float acceleration;
    float velocity;
} p;

Por quê? Como o resultado de p.mass * p.acceleration é uma flutuação e não força conforme o esperado.

A definição de classes para designar um propósito (mesmo que seja um valor, como quantidade mencionado anteriormente) faz mais sentido e nos permite fazer algo como:

struct amount
{
    int value;

    amount() : value( 0 ) {}
    amount( int value0 ) : value( value0 ) {}
    operator int()& { return value; }
    operator int()const& { return value; }
    amount& operator = ( int const newvalue )
    {
        value = newvalue;
        return *this;
    }
};

Você pode acessar o valor em quantidade implicitamente pelo operador int. Além disso:

struct wage
{
    amount balance;

    operator amount()& { return balance; }
    operator amount()const& { return balance; }
    wage& operator = ( amount const&  newbalance )
    {
        balance = newbalance;
        return *this;
    }
};

Uso do Getter/Setter:

void wage_test()
{
    wage worker;
    (amount&)worker = 100; // if you like this, can remove = operator
    worker = amount(105);  // an alternative if the first one is too weird
    int value = (amount)worker; // getting amount is more clear
}

Essa é uma abordagem diferente, não significa que seja boa ou ruim, mas diferente.

1
gus

Uma possibilidade adicional pode ser:

int& amount();

Não tenho certeza se o recomendaria, mas tem a vantagem de que a notação incomum pode impedir os usuários de modificar dados.

str.length() = 5; // Ok string is a very bad example :)

Às vezes, talvez seja apenas a boa escolha a ser feita:

image(point) = 255;  

Outra possibilidade novamente, use a notação funcional para modificar o objeto.

edit::change_amount(obj, val)

Dessa forma, a função perigosa/de edição pode ser removida em um espaço para nome separado com sua própria documentação. Este parece vir naturalmente com a programação genérica.

0
log0

Deixe-me falar sobre uma possibilidade adicional, que parece mais consciente.

Precisa ler e modificar

Simplesmente declare essa variável public:

class Worker {
public:
    int wage = 5000;
}

worker.wage = 8000;
cout << worker.wage << endl;

Precisa apenas ler

class Worker {
    int _wage = 5000;
public:
    inline int wage() {
        return _wage;
    }
}

worker.wage = 8000; // error !!
cout << worker.wage() << endl;

A desvantagem dessa abordagem é que você precisa alterar todo o código de chamada (adicionar parênteses, ou seja) quando quiser alterar o padrão de acesso.

0
Rok Kralj