Primeiro, vamos deixar claro o paradigma.
- Estruturas de dados -> um layout de memória que pode ser percorrido e manipulado por funções com conhecimento adequado.
- Objetos -> um módulo independente que oculta sua implementação e fornece uma interface que pode ser comunicada.
Onde um getter / setter é útil?
Os getters / setters são úteis nas estruturas de dados? Não.
Uma estrutura de dados é uma especificação de layout de memória comum e manipulada por uma família de funções.
Geralmente, qualquer nova função antiga pode surgir e manipular uma estrutura de dados, se o fizer de uma maneira que as outras funções ainda possam entendê-la, então a função se juntará à família. Caso contrário, é uma função desonesta e uma fonte de bugs.
Não me interpretem mal, poderia haver várias famílias de funções lutando sobre essa estrutura de dados com delatores, casacos de turno e agentes duplos em todos os lugares. Tudo bem quando cada um tem sua própria estrutura de dados para brincar, mas quando o compartilha ... imagine várias famílias de criminosos discordando sobre política, pode se tornar uma bagunça muito rápida.
Dada a bagunça que as famílias de funções estendidas podem alcançar, existe uma maneira de codificar a estrutura de dados para que as funções não autorizadas não estraguem tudo? Sim, eles são chamados de objetos.
Os getters / setters são úteis nos objetos? Não.
O objetivo de agrupar uma estrutura de dados em um objeto era garantir que nenhuma função invasora pudesse existir. Se a função quisesse se juntar à família, ela teria que ser cuidadosamente examinada primeiro e depois se tornar parte do objeto.
O objetivo / objetivo de um getter e de um setter é permitir que funções fora do objeto alterem diretamente o layout da memória do objeto. Isso soa como uma porta aberta para permitir que bandidos ...
The Edge Case
Existem duas situações em que um getter / setter público faz sentido.
- Uma parte da estrutura de dados dentro do objeto é gerenciada pelo objeto, mas não é controlada pelo objeto.
- Uma interface que descreve uma abstração de alto nível de uma estrutura de dados em que se espera que alguns elementos não estejam no controle do objeto de implementação.
Contêineres e interfaces de contêiner são exemplos perfeitos dessas duas situações. O contêiner gerencia internamente as estruturas de dados (lista vinculada, mapa, árvore), mas passa o controle sobre o elemento específico para todos. A interface abstrai isso e ignora completamente a implementação e descreve apenas as expectativas.
Infelizmente, muitas implementações entendem isso errado e definem a interface desses tipos de objetos para fornecer acesso direto ao objeto real. Algo como:
interface Container<T>
{
typedef ...T... TRef; //<somehow make TRef to be a reference or pointer to the memory location of T
TRef item(int index);
}
Isto está quebrado. As implementações do Container devem entregar explicitamente o controle de seus internos a quem os usar. Ainda não vi uma linguagem de valor mutável em que isso seja bom (linguagens com semântica de valor imutável são, por definição, ótimas do ponto de vista de corrupção de dados, mas não necessariamente do ponto de vista de espionagem de dados).
Você pode melhorar / corrigir os getters / setter usando apenas cópia semântica ou usando um proxy:
interface Proxy<T>
{
operator T(); //<returns a copy
... operator ->(); //<permits a function call to be forwarded to an element
Proxy<T> operator=(T); //< permits the specific element to be replaced/assigned by another T.
}
interface Container<T>
{
Proxy<T> item(int index);
T item(int index); //<When T is a copy of the original value.
void item(int index, T new_value); //<where new_value is used to replace the old value
}
Indiscutivelmente, uma função não autorizada ainda pode causar confusão aqui (com esforço suficiente, a maioria das coisas é possível), mas a cópia-semântica e / ou proxy reduz a chance de vários erros.
- transbordar
- underflow
- as interações com o subelemento são verificadas por tipo / verificáveis por tipo (no tipo perder idiomas, isso é um benefício)
- O elemento real pode ou não ser residente na memória.
Getters / Setters privados
Este é o último bastião de getters e setters trabalhando diretamente no tipo. Na verdade, eu nem chamaria esses getters e setters, mas acessores e manipuladores.
Nesse contexto, às vezes, manipular uma parte específica da estrutura de dados sempre / quase sempre / geralmente exige que ocorra manutenção contábil específica. Digamos que quando você atualiza a raiz de uma árvore, o cache oculto precisa ser eliminado ou quando você acessa o elemento de dados externos, um bloqueio precisa ser obtido / liberado. Nesses casos, faz sentido aplicar o principal DRY e agrupar essas ações.
Dentro do contexto privado, ainda é possível para as outras funções da família desviar esses 'getters e setters' e manipular a estrutura de dados. Por isso, penso neles mais como acessores e manipuladores. Você pode acessar os dados diretamente ou confiar em outro membro da família para acertar essa parte.
Getters / Setters protegidos
Em um contexto protegido, não é muito diferente de um contexto público. Funções estrangeiras possivelmente desonestas desejam acessar a estrutura de dados. Portanto, não, se existem, eles operam como getters / setters públicos.
this->variable = x + 5
ou chamar umaUpdateStatistics
função no setter e, nesses casosclassinstancea->variable = 5
, causará problemas.