Boa noite galera!
Sou iniciante e gostaria de tirar uma dúvida.
Estou aprendendo sobre encapsulamento preciso fazer um controle remoto (exercício de aprendizado) .Em um dos métodos o professor quer fazer a barra de volume aumentar de 10 em 10, e fazer aqueles " | ", ou seja, a parte gráfica mostrando uma barra para indicar a altura do volume (desculpe se não conseguir explicar direito).
Com isso, ele deu o seguinte código:
public void abrirMenu() {
System.out.print (“Volume: " + this.getVolume());
for (int i = 0; i <= this.getVolume(); i+=10) {
System.out.print (”|");
}
Não consegui entender, alguém poderia me explicar?
Obrigado pela ajuda!
Sendo bem sincero, o código acima não tem nada de encapsulamento.
De qualquer maneira, segue uma explicação breve sobre o trecho acima:
public void abrirMenu() { //Assinatura do método abrirMenu, não tem retorno (void)
System.out.print ("Volume: " + this.getVolume()); //Mostra o valor atual contido na variável volume
for (int i = 0; i <= this.getVolume(); i+=10) {//Executa N repetições, de 0 até o valor atual contido na variável volume
System.out.print ("|"); //imprime a representação do ícone de volume.
}
}
Em tempo: o conceito de encapsulamento, em OO, se refere aos procedimentos que visam tornar atributos (na verdade, métodos e a lógica desenvolvida) invisíveis a quem quer que venha a utilizar as instâncias de objeto de uma determinada classe.
Normalmente, diz-se que encapsulamento se baseia em:
Tornar os atributos privados (colocar o modificador private antes da declaração de cada atributo)
private String nome;
Criar métodos assessores (getters e setters)
public void setNome(String nome) {
this.nome = nome;
}
public String getNome() {
return this.nome;
}
Porém, isso não garante nenhuma restrição aos atributos, apenas burocratiza a chamada, obrigando qualquer classe que venha a tentar alterar ou ler o valor de uma variável a passar pelos métodos citados.
O mais próximo da real finalidade do encapsulamento é o uso de interfaces.
Entendi!
Muito obrigado!
O código realmente não tem nada a ver com encapsulamento, mas ele usou um controle remoto para explicar de uma forma mais didática o que significa o encapsulamento, mas acho que consegui entender bem com sua explicação Darlan!
O encapsulamento é como se fosse uma proteção que possui os métodos abstratos, para que dificulte o acesso dos atributos e dos objetos instanciados da classe, certo?
É quase isso.
Entenda encapsulamento como a proteção da lógica da tua aplicação.
Considere a classe ControleRemoto mesmo. Você sabe que só precisa apontar para a TV, por exemplo, pressionar um dos botões e algo acontecerá (altera o volume, muda de canal, aciona um menu com opções para alterar o brilho, a saturação, idioma, etc). Mas, você sabe como é o funcionamento interno deste controle remoto ou de qualquer outro? Creio que não, até por quê, isso é irrelevante para você. Você não precisa saber da estrutura interna dele, apenas como utilizar o mesmo.
Ótima explicação!
Poxa Darlan, muito obrigado, de verdade, consegui entender e até guardei a sua explicação em um bloco de notas kkkkkk.
Estou entendendo aos poucos e espero conseguir fixar tudo de OO.
Não tenha pressa e não se apegue tanto a isso.
Na grande maioria das vezes, você vai acabar encontrando projetos onde muitos desses conceitos são ignorados.
Quero ressaltar que o encapsulamento, na verdade, não tem como principal objetivo proteger seu Objeto de ser usado indevidamente e colocado em um estado inconsistente, porque mesmo que você use os modificadores private nos atributos eles poderão ser acessados via reflexão.
A real utilidade do encapsulamento se trata dos Contratos, ao encapsular e definir interfaces você está estabelecendo um Contrato do que você oferece ao Código-Cliente, que esse Cliente deveria respeitar não usando a reflexão ou fazendo Cast inseguros para esperar que seu Código-Fornecedor lhe entregue o que este Fornecedor não promete fornecer no Contrato.
Então, na verdade tem haver com desacoplamento e segurança contra quebras na Arquitetura devido a alterações. Tem haver com construir uma Arquitetura flexível como está nesse esboço na wikipedia:
Para simplificar: Se você criar um método dentro da sua classe que não precisa ser acessado por código externo à classe, e, acha que esse método tem boa chance de ser excluído no futuro para haver uma implementação melhor que não o utilize, é uma ótima ideia deixar esse método privado como uma forma de dizer a todas as outras classes que elas não devem chamá-lo (Já imaginou ter um monte de classes chamando um método que acabará por ser excluído depois? Vai dar bastante trabalho consertar todas elas).
O mesmo pode ocorrer se você acha que poderá mudar os parâmetros do método ou seu tipo de retorno.
É uma situação em que você esconde o método através do encapsulamento, é uma forma de dizer que você não promete - no seu Contrato - a existência desse método nem a assinatura dele, e que as Classes clientes Não devem depender dele.
Assim você fica livre para fazer o quiser com esse método depois, sabendo que não irá quebrar (diretamente) nenhuma outra classe da Arquitetura porque nenhuma outra classe o está chamando.
Então, procure sempre esconder através do encapsulamento tudo o que poderá mudar, para que nenhum código-cliente torne-se dependente de coisas que irão mudar e quebre quando a mudança ocorrer. Quanto menos você expor no encapsulamento, mais liberdade terá para fazer alterações sabendo que não irá quebrar nada externo. Então, exponha somente o que é necessário para os códigos-clientes.
Mas você realmente deve expor o que os códigos Clientes precisam, por exemplo, se seu Cliente realmente precisa de um Cachorro, não retorne para ele um Animal forçando-o a fazer um Cast:
void metodoCliente() {
Animal a = metodoFornecedor(); //Retorna um objeto Animal
Cachorro c = (Cachorro) a; //Cast inseguro
....
}
Veja no código acima que o contrato do metodoFornecedor() garante que ele retornará um Animal, ele não promete retornar Cachorro. Então, o que acontece se você mudar a implementação para que ele retorne um Gato? Esse método Cliente vai quebrar, porque ele espera algo que o Fornecedor nunca prometeu entregar.
Teoricamente é possível esclarecer o Contrato na Documentação, como o método Collection#add(E) fala, mas isso é uma solução bem pior porque não fica explícito o Contrato no próprio código.
Por exemplo, eu já fiz algo assim:
List<String> lista = Arrays.asList("a", "b", "c");
lista.add("d"); //laçou uma UnsupportedOperationException
O que aconteceu acima foi que Arrays.asList("a", "b", "c"); me retornou uma lista que tem o método add(E) como toda Collection tem, mas que não aceita adicionar elementos nela, então não posso chamar esse método na lista retornada mesmo ele estando na interface.
Mas sabe qual seria o pior caso? Este:
Se Arrays.asList("a", "b", "c") tivesse retornado uma lista que aceita adicionar elementos esse código teria funcionado, e, se no futuro, fosse alterado o que Arrays.asList() retorna para retornar uma lista que não aceita adicionar elementos, esse código que estava funcionando deixaria de funcionar sem motivo aparente.
No meu caso, eu não estava usando testes automatizados nem criando um software para vender para alguém, mas, hipoteticamente falando:
Se esse código fosse chamado só de vez em quando, e se não houvesse Testes automatizados para validá-lo, esse seria um bug que poderia passar desapercebido e talvez até causar um grande estrago se o programa tivesse entrado em uso por um cliente.
É por esse tipo de coisa que os Contratos são tão importantes, o melhor é que os Contratos já estejam na própria Arquitetura, no que é encapsulado, no que é exposto, nos retornos dos métodos e parâmetros de entrada, nas interfaces, etc.
Se não for suficiente, pode-se implementar Contratos com Testes Automatizados (que validam se o contrato está sendo cumprido), ou na Documentação, que pra mim parece ser o pior caso.
Provavelmente os projetistas da API Collections chegaram a conclusão que seria melhor fazer desse jeito, porque, talvez, fazer diferente teria sido ainda pior.
Sensacional Douglas!
Deixa eu ver se entendi…
Então o encapsulamento é como se você fragmentasse o seu código fonte para que tudo não dependesse sempre dos mesmos métodos ou de qualquer outra dependência, para que ele seja mais volátil, proposto a mudanças. É por isso que chamam de teste de software UNITÁRIO? para que você consiga validar (testar) o código por blocos?
Não, fragmentar o código tem mais a ver com Composição, ou seja, separar o sistema em blocos compostos de sub-blocos, onde se pode montar esses blocos ou substituir alguns blocos por outros.
Encapsular se trata de esconder coisas, esconder a implementação e deixar exposto apenas a interface, afim de evitar que os códigos externos se tornem dependentes da implementação; eles acabarão ficando dependentes da interface. Portanto, se você mudar a implementação eles não irão quebrar (quebrar no sentido de nem compilar), mas se mudar a interface, é quase certo que irão.
Encasulamento, Testes Automatizados e Testes Unitários são todos coisas diferentes. Mas sim, você divide o código em blocos (componentes) para testar cada um individualmente; também dá para montá-los depois e testar o conjunto ou subconjunto.
Acho que esse termo “bloco” não é comum, costumo ver as pessoas chamarem de Componentes, Módulos (que são formados de Componentes), e eu chamo até de Estruturas de Componentes, já que combiná-los forma uma estrutura.
Acho que entendi. Então caso eu programe algo bem encapsulado, deixando somente a interface visível, caso um outro programador ou eu mesmo tenha que realizar alguma mudança, não vou impactar diretamente na implementação, e sim somente na interface. Ou seja, posso manipular “superficialmente” (se é que podemos chamar assim), porem a base (a implementação) estará intacta e certamente meu código estará intacto com grande chances de ser afetado por alguma mudança.
Obrigado pela explicação, parece estar ficando mais claro!
Parece que você está dizendo exatamente o contrário. É a interface que se busca manter inalterada, pois é dela que os códigos clientes dependem; a implementação é escondida para ser alterada sem causar impactos negativos por todo o sistema (ter que editar códigos clientes por todo o sistema).
Ou seja, quando é necessário fazer alterações, é a interface que permanece “intacta” (inalterada), e não a implementação. Você altera a implementação (que deve estar escondida), e não a interface.
Claro que às vezes você vai precisar alterar a interface também, mas isso geralmente vai levar à várias alterações por todo o sistema para que ele se adapte; por isso busca-se evitar alterar a interface.
Muitas vezes, apenas estende-se a interface, criando novos métodos e construtores, por exemplo; isso não irá quebrar o código já existente no sistema.
AGORA entendi! Eu estava pensando exatamente ao contrário mesmo.
Então devemos deixar a interface mais intacta possível para não alterar a forma usual que o cliente manipula, e caso tenha que alterar, só mexemos na implementação, ou seja, o que está por trás da interface, certo?
Desculpe o “trabalho” que estou dando, rs, mas agora acho que entendi!
Obrigado Douglas!!!