Gets and Sets Erradication

É perfeitamente possível que uma classe acesse atributos privados de outra usando a API de Reflection. Quando você mapeia seus atributos usando JPA, por exemplo, é exatamente isto que acontece.

Na verdade, não. Imagine que você tem uma classe com um atributo numérico, digamos, salário, do tipo double e você cria um método

public double getSalario() { return this.salario; }

e descobre que o tipo double, por exemplo, não lhe fornece precisão suficiente e é necessário o uso de BigDecimal, não há como fazer isto sem quebrar o contrato da classe.

Desculpem senhores,

eu li todos os post e como não vi nenhuma ocorrência, acho que posso estar fazendo coisas erradas.

Um dos motivos que utilizo os métodos gets/sets é para “criar” notificações para objetos monitores sobre a modificação de determinados atributos, como ocorre em vários componentes da Swing. O que é muito similar aos “eventos” da programação por evento.

logo ficou a dúvida, existe outra forma de identificar a mudança de conteúdo (ou estado) de uma propriedade no exato momento em que ela ocorre e em alguns casos antes que ela ocorra?

exemplo de método:

public void setNascimento(Date nasc) { for(AtributoModificadoNotificar aviso : listeners ) aviso.before... this.nascimento = nasc; for(AtributoModificadoNotificar aviso : listeners ) aviso.after... }

vw

Ps: Eu sei que posso criar uma thread que fica lendo o conteúdo de uma propriedade e comparando com um valor interno e quando ocorrer a mudança notificar outro objeto, mas não é o caso.

É perfeitamente possível que uma classe acesse atributos privados de outra usando a API de Reflection. Quando você mapeia seus atributos usando JPA, por exemplo, é exatamente isto que acontece.

Na verdade, não. Imagine que você tem uma classe com um atributo numérico, digamos, salário, do tipo double e você cria um método

public double getSalario() { return this.salario; }

e descobre que o tipo double, por exemplo, não lhe fornece precisão suficiente e é necessário o uso de BigDecimal, não há como fazer isto sem quebrar o contrato da classe.[/quote]

Continuo achando que eh impossivel acessasr os atributos privados… no maximo quando vc mapeia seus atributos usando JPA, vc consegue porque vc tb cria os get/set que sao public… por Reflection vc nao consegue ler/escrever em um atributo privado… se isso fosse possivel, toda a “seguranca” de manter um atributo privado seria jogada fora.

No caso da necessidade de usar um BigDecimal, para nao quebrar o contrato da classe vc precisaria manter o public double getSalario(); e adicionar um public BigDecimal getSalarioNovo();… em seguida, tornar o antigo getSalario deprecated.

[quote=RafaelVS]
Então, imagine que Conta tenha duas operações: creditar e debitar. Ambas atualizam o atributo saldo, uma incrementando uma valor e outra decrementando. Além disso, a operação debitar precisa validar se a conta tem saldo suficiente.

A) Dependendo do projeto, podemos na classe Conta criar simplesmente um método setSaldo(double valor) que atualizará o saldo com o valor informado, sem fazer nenhuma validação.

Na classe que trata das regras de negócio (aqui chamada de CadastroConta) deverá ter os métodos creditar() e debitar(). O método creditar() simplesmete somaria o valor e chamaria o setSaldo(novoValor) da conta. O método debitar() faz a validação, verificando se a conta tem saldo suficiente para debitar o valor informado e chama o método setSaldo(novoValor), onde o novoValor já é o valor final, após diminuir o saldo.

B) Outra opção, seria na própria classe Conta ter os métodos creditar() e debitar(), que funcionam como um “setSaldo” só que já contém implementação de regras de validação, se necessário. Aqui simplificaríamos os métodos na classe CadastroConta, que não precisariam fazer validação e simplesmente delegar a operação para a classe básica, que fará a validação.

Uma vantagem em tomar a decisão A é que vc está aumentando a possibilidade de reuso da classe Conta… já que ela não tem nenhum tipo de validação… por exemplo, se vc colocar o sistema em um outro banco onde a validação para a operação debitar é verificar se tem saldo com algum limite especial, a sua classe Conta não seria afetada… vc só precisaria mudar no método da classe CadastroConta. Uma desvantagem é que vc poderia, sem querer, através de algum método chamar diretamente o setSaldo(valor) podendo passar um valor sem fazer validação (mas se vc tomar a decisão de poder reusar a classe Conta, vc terá que tomar esse cuidado de não utilizar o método de maneira indevida)

Uma vantagem em tomar a decisão B é que ficará mais seguro… a partir de qualquer lugar vc pode chamar o método debitar() passando um valor inválido que o método irá validar corretamente e só atualizará os dados se tal operação for válida. Uma desvantagem é que se vc precisar reutilizar essa classe em um banco que usa uma política diferente para validação (como a que citei acima) vc precisaria escrever uma nova classe Conta para aquele banco.

A mesma idéia serve para o método creditar()… se ele é definido no CadastroConta e eu precisar reusar a classe Conta em um sistema que tem uma política diferente para creditar (por exemplo, se o saldo a ser creditado for maior do que X, será dado algum bônus para o cliente da conta) eu poderei fazer isso. Pois a classe Conta tem apenas um método setSaldo() sem validação e eu posso usa-lo tanto para creditar quanto para debitar (independente da regra envolvida)

Por isso falei que isso é uma questão de decisão de projeto… não tem a forma certa ou errada, vc é quem decide, nesse caso, se vai querer integridade dos dados ou se vai querer maior possibilidade de reuso.[/quote]

Não é por nada não. Mas o argumento que você usou pra opção A é pouquíssimo convincente. Você acredita que se deva fazer a separação entre uma classe com métodos (CadastroConta) e uma classe com propriedades (Conta), pois a primeira pode variar entre bancos, e a segunda seria estático. Ora, quem garante que as propriedades de Conta também não mudam entre bancos? Isso não faz sentido!

Talvez um jeito de garantir reúso seja a utilização de design patterns como o Template Method, do jeito que você falou, o reúso não acontece.

Mas a idéia da opção A não eh GARANTIR o reuso… é AUMENTAR A POSSIBILIDADE de reuso. Se vc coloca validação no método vc vai estar amarrando a implementação… se não fizer isso, aumenta a possibilidade de reusar a classe em outro banco.

Se a conta de um banco A tem as mesmas propriedades da conta de um banco B mas possuem regras de validação diferentes, eu poderia reusar aquela classe se a validação não estiver na classe Conta… Mas se as contas possuem propriedades diferentes, aí eu n poderia reusar… mas pelo menos eu tenho a chance de reusar no primeiro caso.

E esse exemplo que eu dei foi bem simples, que fica facil de entender… tem casos onde as propriedades das classes mudam menos do que as regras de negócio.

Além disso, estou dando exemplos que motivem a implementação usando uma arquitetura em camadas (mantendo as camadas: classes basicas, repositorio, regras de negocio, fachada, gui) e separa codigo de negocio da classe basica eh uma boa opcao para aumentar a possibilidade de reuso.

[quote=RafaelVS]Continuo achando que eh impossivel acessasr os atributos privados… no maximo quando vc mapeia seus atributos usando JPA, vc consegue porque vc tb cria os get/set que sao public… por Reflection vc nao consegue ler/escrever em um atributo privado… se isso fosse possivel, toda a “seguranca” de manter um atributo privado seria jogada fora.

No caso da necessidade de usar um BigDecimal, para nao quebrar o contrato da classe vc precisaria manter o public double getSalario(); e adicionar um public BigDecimal getSalarioNovo();… em seguida, tornar o antigo getSalario deprecated.[/quote]

Por increça que parível, a JPA é capaz sim de acessar um atributo privado sem get/set. Só não sei como, mas faz. Eu já fiz esse teste e funciona. Faça você também e verá. Tudo bem que a “segurança” seria jogada fora, mas por mim, seria por uma boa causa.

Agora, quanto a criar um novo método getSalarioNovo() e fazer getSalario() deprecated é uma idéia ridícula! Normalmente, um método deprecated é simplesmente marcado como tal, e não se mexe mais nela. Mas nesse caso não, ou você: a) cria tanto o atributo float e BigDecimal dentro da classe e se vira pra sincronizá-los, ou b) você troca float pra BigDecimal e reimplementa um método que imediatamente vai virar deprecated e não terá serventia nenhuma. Além do mais, você não evita a reescrita de outras classes, apenas posterga para uma data indefinida.

O grande nabo do excesso de getters e setters é que muitas vezes a pessoa cria regras de negócio fora da classe, mas não apenas em uma outras classe, mas em várias, e de maneira desorganizada. Muitas vezes, numa equipe média ou grande, ocorre até repetição de código. A colocação dessas regras dentro da classe é eficiente para que esses problemas não ocorram.

[quote=RafaelVS]Mas a idéia da opção A não eh GARANTIR o reuso… é AUMENTAR A POSSIBILIDADE de reuso. Se vc coloca validação no método vc vai estar amarrando a implementação… se não fizer isso, aumenta a possibilidade de reusar a classe em outro banco.

Se a conta de um banco A tem as mesmas propriedades da conta de um banco B mas possuem regras de validação diferentes, eu poderia reusar aquela classe se a validação não estiver na classe Conta… Mas se as contas possuem propriedades diferentes, aí eu n poderia reusar… mas pelo menos eu tenho a chance de reusar no primeiro caso.

E esse exemplo que eu dei foi bem simples, que fica facil de entender… tem casos onde as propriedades das classes mudam menos do que as regras de negócio.

Além disso, estou dando exemplos que motivem a implementação usando uma arquitetura em camadas (mantendo as camadas: classes basicas, repositorio, regras de negocio, fachada, gui) e separa codigo de negocio da classe basica eh uma boa opcao para aumentar a possibilidade de reuso.[/quote]

Reusar atributos é um tipo muito pobre de reúso. E quanto a aumentar ou não a possibilidade de reúso, isso é subjetivo demais. A minha crença é não.

A arquitetura em camadas que você fala não é da maneira que eu imagino e costumo fazer. As regras de negócio não ficam em cima da repositorio e das classes básicas. Ficam justamente dentro das classes básicas! E não há razão de não ser assim, as regras de negócio independem de contexto cliente-servidor, de clusterização ou de qualquer que seja. Agora, persistência, camada de serviço (facade) e gui dependem de contexto específico e merecem ficar em cima das classes básicas.

As validações que tratam do estado do a tributo devem ficar dentro da classe basica, mas as validacoes que envolvem outros cadastros/repositorios, devem ficar numa camada separada, acima do repositorio.

Ex.:

Locadora.

Pra fazer uma locacao, preciso informar a midia (atraves do codigo) e um cliente (atraves do cpf). Pra confirmar o cadastro, eh preciso que o codigo e o cpf estejam cadastrados no sistema.

Na classe CadastroLocacao tem o metodo locar(codigoMidia, cpfCliente)… Esse método faz as consultas nos devidos repositorios para saber se os parametros existem no sistema. Caso essa validação tenha sucesso, eu insiro uma Locacao chamo os sets de Midia e Cliente na classe Basica de Locacao (que nao possuem nenhuma validacao) e insiro a Locacao no RepositorioLocacao.

Se essa validação fosse colocada na classe básica Locacao, estaria criando muita dependencia na classe básica e diminuiria a possibilidade de reuso.

Pode acreditar: é possível! :smiley:
Leia a documentação do Hibernate:

Faça um teste do tipo:

[code]@Entity
public class Teste {

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;

private String nome;

public Teste() {}

public Teste(String nome) {
	this.nome = nome;
}

@Override
public String toString() {
	return String.format("[Teste id: %d, nome: %s]", this.id, this.nome);
}

}[/code]
E depois:

[code]entityManager.persist(new Teste(“Teste1”));
entityManager.persist(new Teste(“Teste2”));
entityManager.persist(new Teste(“Teste3”));

List testes = entityManager.createQuery(“select t from Teste t”).getResultList();
for (Teste teste : testes) {
System.out.println(teste);
}[/code]

É possivel acessar sim Rafael, usando o Field.setAcessible(true). Da pra bloquear isso com security policy, mas ai o hibernate/jpa pode nao funcionar direito se voce fez anotacoes por atributos em vez de getters. Nao precisa dos getters e setters.

Oi, Paulo… estou aqui pesquisando como o Hibernate faz pra acessar o membro private… ainda nao cheguei a uma conclusao definitiva, mas ate agora ta parecendo que isso eh possivel por causa das annotations que colocamos no atributo ou na classe… Se for isso mesmo, entao acho (ja nao tenho mais certeza agora hehe) que um membro private puro (sem intervencao de annotations) não consiga ser acessado fora da classe nem com Reflection.

Talvez seja justificavel o fato do hibernate conseguir acessar o membro private por causa da annotation @Entity ou da @GeneratedValue (que acredito que talvez possam mudar o comportamento, inclusive o nível de acesso dos membros)

Fiz o sequinte teste rapidamente do setAccessible e se o atributo for private não consigo acessá-lo por Reflection:

public Class Teste {

private String aaa = "aaa"; //Assim a linha Field f = Teste.class.getField("aaa"); lança "java.lang.NoSuchFieldException: aaa"
//public String aaa = "aaa"; //Assim exibe o vaor do atributo aaa.

public static void main(String[] args) throws Exception {
  Field f = Teste.class.getField("aaa");
  f.setAccessible(true);
  System.out.println(f.get(new Teste()));
}

}

Na verdade, o modo como o Hibernate acessa suas propriedades depende de onde está a anotação @Id. Se marcar no atributo ele acessa via atributo, mesmo sendo private.

Aliás, muitos frameworks fazem acesso a atributos private via Reflection

Use getDeclaredField. E da proxima que o Paulo te disser que eh possivel, nao duvide. :smiley:

Use getDeclaredField. E da proxima que o Paulo te disser que eh possivel, nao duvide. :D[/quote]

Caramba!! hehehe impressionante… testei com getDeclaredField e consegui setar e capturar valores do atributo private hehehe. Será que tb consigo acessar os atributos privados das classes de Java? Ou eles estão protegidos com as políticas que o Paulo comentou?

Valeu.

As políticas de segurança valem para todas as classes. É possível acessar atributos privados de qualquer classe com as políticas default.

public static void main(String[] args) { String s = "Teste"; try { Field f = String.class.getDeclaredField("value"); f.setAccessible(true); char[] conteudoString = (char[]) f.get(s); for (char ch: conteudoString) System.out.print(ch); } catch (Exception e) { e.printStackTrace(System.err); } }[

Agora, vai acreditar em mim ou vai esperar o Paulo confirmar outra vez? :twisted: :twisted:

Isto significa que a cada mudança na forma como o atributo é armazenado, ou seja, a cada alteração na estrutura interna da classe, a interface pública da classe é alterada. Se isto não é quebra de encapsulamento, então eu não sei o que seria.

Um interessante link sobre getters e setters é Why getter and setter methods are evil. O autor, Allen Holub, é bastante radical em muitos de seus pontos de vista. Foi um artigo, porém, que abriu minha mente para muitos aspectos da OO. O JavaWorld, por sinal, como já dito, é uma ótima refrerência.

Aproveito a oportunidade para parabenizar o Paulo Silveira e o Phillip Calçado pelos excelentes textos. Material com esta qualidade em português é algo raro.