Acessores e modificadores (também conhecidos como setters e getters) são úteis por três razões principais:
- Eles restringem o acesso às variáveis.
- Por exemplo, uma variável pode ser acessada, mas não modificada.
- Eles validam os parâmetros.
- Eles podem causar alguns efeitos colaterais.
Universidades, cursos on-line, tutoriais, artigos de blog e exemplos de código na Web estão todos enfatizando a importância dos acessadores e modificadores, quase parecem um "must have" para o código hoje em dia. Assim, é possível encontrá-los mesmo quando eles não fornecem nenhum valor adicional, como o código abaixo.
public class Cat {
private int age;
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
Dito isto, é muito comum encontrar modificadores mais úteis, aqueles que realmente validam os parâmetros e lançam uma exceção ou retornam um booleano se uma entrada inválida foi fornecida, algo como isto:
/**
* Sets the age for the current cat
* @param age an integer with the valid values between 0 and 25
* @return true if value has been assigned and false if the parameter is invalid
*/
public boolean setAge(int age) {
//Validate your parameters, valid age for a cat is between 0 and 25 years
if(age > 0 && age < 25) {
this.age = age;
return true;
}
return false;
}
Mas mesmo assim, quase nunca vejo os modificadores serem chamados de um construtor, portanto, o exemplo mais comum de uma classe simples com a qual me deparo é:
public class Cat {
private int age;
public Cat(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
/**
* Sets the age for the current cat
* @param age an integer with the valid values between 0 and 25
* @return true if value has been assigned and false if the parameter is invalid
*/
public boolean setAge(int age) {
//Validate your parameters, valid age for a cat is between 0 and 25 years
if(age > 0 && age < 25) {
this.age = age;
return true;
}
return false;
}
}
Mas alguém poderia pensar que essa segunda abordagem é muito mais segura:
public class Cat {
private int age;
public Cat(int age) {
//Use the modifier instead of assigning the value directly.
setAge(age);
}
public int getAge() {
return this.age;
}
/**
* Sets the age for the current cat
* @param age an integer with the valid values between 0 and 25
* @return true if value has been assigned and false if the parameter is invalid
*/
public boolean setAge(int age) {
//Validate your parameters, valid age for a cat is between 0 and 25 years
if(age > 0 && age < 25) {
this.age = age;
return true;
}
return false;
}
}
Você vê um padrão semelhante em sua experiência ou sou apenas eu tendo azar? E se você faz, então o que você acha que está causando isso? Existe uma desvantagem óbvia para o uso de modificadores dos construtores ou eles são apenas considerados mais seguros? É algo mais?
fonte
Respostas:
Raciocínio filosófico muito geral
Normalmente, solicitamos que um construtor forneça (como pós-condições) algumas garantias sobre o estado do objeto construído.
Normalmente, também esperamos que os métodos de instância possam assumir (como pré-condições) que essas garantias já são válidas quando são chamadas e que eles só precisam se certificar de não quebrá-las.
Chamar um método de instância de dentro do construtor significa que algumas ou todas essas garantias ainda não foram estabelecidas, o que dificulta o raciocínio sobre se as pré-condições do método de instância são satisfeitas. Mesmo se você acertar, pode ser muito frágil diante de, por exemplo. reordenar chamadas de método de instância ou outras operações.
Os idiomas também variam na maneira como resolvem chamadas para métodos de instância que são herdados das classes base / substituídos por subclasses, enquanto o construtor ainda está em execução. Isso adiciona outra camada de complexidade.
Exemplos específicos
Seu próprio exemplo de como você acha que isso deve parecer está errado:
isso não verifica o valor de retorno de
setAge
. Aparentemente, ligar para o setter não é garantia de correção, afinal.Erros muito fáceis, como depender da ordem de inicialização, como:
onde meu registro temporário quebrou tudo. Ops!
Também existem linguagens como C ++, em que chamar um setter do construtor significa uma inicialização padrão desperdiçada (que pelo menos para algumas variáveis de membro vale a pena evitar)
Uma proposta simples
É verdade que a maioria dos códigos não é escrita assim, mas se você deseja manter seu construtor limpo e previsível e ainda reutilizar sua lógica de pré e pós-condição, a melhor solução é:
ou melhor ainda, se possível: codifique a restrição no tipo de propriedade e valide seu próprio valor na atribuição:
E, finalmente, para completar, um invólucro de auto-validação em C ++. Observe que, embora ainda esteja fazendo a validação tediosa, porque essa classe não faz mais nada , é relativamente fácil verificar
OK, não está realmente completo, deixei de fora várias cópias e movo construtores e atribuições.
fonte
Quando um objeto está sendo construído, por definição, ele não está totalmente formado. Considere como o levantador que você forneceu:
E se parte da validação
setAge()
incluísse uma verificação naage
propriedade para garantir que o objeto só aumentasse de idade? Essa condição pode se parecer com:Parece uma mudança bastante inocente, já que os gatos só podem se mover no tempo em uma direção. O único problema é que você não pode usá-lo para definir o valor inicial de,
this.age
porque assume quethis.age
já possui um valor válido.Evitar setters em construtores deixa claro que a única coisa que o construtor está fazendo é definir a variável de instância e ignorar qualquer outro comportamento que ocorra no setter.
fonte
Algumas boas respostas já estão aqui, mas ninguém até agora parecia ter notado que você está perguntando aqui duas coisas em uma, o que causa alguma confusão. IMHO sua postagem é melhor vista como duas perguntas separadas :
se houver uma validação de uma restrição em um setter, também não deveria estar no construtor da classe para garantir que ela não possa ser ignorada?
por que não chamar o setter diretamente para esse fim do construtor?
A resposta para as primeiras perguntas é claramente "sim". Um construtor, quando não lança uma exceção, deve deixar o objeto em um estado válido e, se certos valores são proibidos para determinados atributos, não faz absolutamente nenhum sentido deixar o ctor contornar isso.
No entanto, a resposta para a segunda pergunta é normalmente "não", desde que você não tome medidas para evitar a substituição do levantador em uma classe derivada. Portanto, a melhor alternativa é implementar a validação de restrição em um método privado, que pode ser reutilizado no setter e no construtor. Não vou aqui repetir o exemplo @Useless, que mostra exatamente esse design.
fonte
Aqui está um pouco de código Java bobo que demonstra os tipos de problemas com os quais você pode se deparar usando métodos não finais em seu construtor:
Quando executo isso, obtenho um NPE no construtor NextProblem. Este é um exemplo trivial, é claro, mas as coisas podem se complicar rapidamente se você tiver vários níveis de herança.
Eu acho que a razão maior pela qual isso não se tornou comum é que torna o código um pouco mais difícil de entender. Pessoalmente, quase nunca possuo métodos setter e minhas variáveis-membro são quase invariavelmente finais (trocadilhos). Por esse motivo, os valores precisam ser definidos no construtor. Se você usa objetos imutáveis (e há muitas boas razões para fazê-lo), a questão é discutível.
De qualquer forma, é um bom objetivo reutilizar a validação ou outra lógica e você pode colocá-lo em um método estático e invocá-lo do construtor e do setter.
fonte
name
campo).this
referência para outro método, porque ainda não pode ter certeza de que ele foi totalmente inicializado.Depende!
Se você estiver executando uma validação simples ou emitindo efeitos colaterais impertinentes no setter que precisam ser atingidos toda vez que a propriedade for configurada: use o setter. A alternativa é geralmente extrair a lógica do setter e chamar o novo método seguido pelo conjunto real do setter e do construtor - que é efetivamente o mesmo que usar o setter! (Só que agora você está mantendo seu, reconhecidamente pequeno, levantador de duas linhas em dois lugares.)
No entanto , se você precisar executar operações complexas para organizar o objeto antes que um setter específico (ou dois!) Possa ser executado com êxito, não use o setter.
E, em ambos os casos, tenha casos de teste . Independentemente de qual rota você percorrer, se você tiver testes de unidade, terá uma boa chance de expor as armadilhas associadas a qualquer opção.
Acrescentarei também que, se minha memória de longe é remotamente precisa, esse também é o tipo de raciocínio que aprendi na faculdade. E isso não está de acordo com os projetos bem-sucedidos em que tive oportunidade de trabalhar.
Se eu tivesse que adivinhar, perdi um memorando de "código limpo" em algum momento que identificava alguns erros típicos que podem surgir do uso de setters em um construtor. Mas, da minha parte, não fui vítima de nenhum desses erros ...
Então, eu argumentaria que essa decisão em particular não precisa ser abrangente e dogmática.
fonte