Vamos supor uma classe (primeira classe) com o nome ContaBanco, que pode ser criada por várias pessoas (portando, teria várias contas), com funções básicas como sacar, abrir e fechar contas, pagar mensalidades, etc. Ela teria um formato parecido com esses atributos e métodos:
ContaBanco |
---|
+ numConta # tipo - dono - saldo - status |
+ abrirConta(t) + fecharConta() + depositar(v) + sacar(v) + pagarMensal() |
Crie um novo projeto Java com classe principal, e nela crie uma nova classe com o nome ContaBanco, esse é o código da classe ContaBanco:
package bancopoo;
public class ContaBanco {
public int numConta;
protected String tipo;
private String dono;
private float saldo;
private boolean status;
public void estadoAtual() {
System.out.println("-------------------");
System.out.println("Conta: " + this.getNumConta());
System.out.println("Tipo: " + this.getTipo());
System.out.println("Dono: " + this.getDono());
System.out.println("Saldo: " + this.getSaldo());
System.out.println("Status: " + this.getStatus());
}
public void abrirConta(String t) {
this.setTipo(t);
this.setStatus(true); // Prefira usar métodos do que mexer direto no atributo.
if(t.equals("CC")) {
this.setSaldo(50);
}
else if(t.equals("CP")) {
this.setSaldo(150);
}
System.out.println("Conta aberta com sucesso!");
}
public void fecharConta() {
if(this.getSaldo() > 0) {
System.out.println("Conta não pode ser fechada porque ainda tem dinheiro!");
}
else if(this.getSaldo() < 0) {
System.out.println("Conta não pode ser fechada pois tem débido!");
}
else {
this.setStatus(false);
System.out.println("Conta de " + this.getDono() + " fechada com sucesso!");
}
}
public void depositar(float v) {
if(this.getStatus() == true) {
this.setSaldo(this.getSaldo() + v);
// this.saldo = this.saldo + v;
System.out.println("Depósito de R$" + v + " realizado na conta de " + this.getDono());
}
else {
System.out.println("Conta fechada ou inexistente!");
}
}
public void sacar(float v) {
if(this.getStatus() == true) {
if(this.getSaldo() >= v) {
this.setSaldo(this.getSaldo() - v);
// this.saldo = this.saldo - v;
System.out.println("Saque de R$" + v + " realizado na conta de " + this.getDono());
}
else {
System.out.println("Saldo insuficiente para saque!");
}
}
else {
System.out.println("Conta fechada! Impossível sacar!");
}
}
public void pagarMensal() {
int v = 0;
if(this.getTipo().equals("CC")) {
v = 12;
}
else if(this.getTipo().equals("CP")) {
v = 20;
}
// Outra condição
if(this.getStatus() == true) {
this.setSaldo(this.getSaldo() - v);
System.out.println("Mensalidade de R$" + v + " debitada da conta de " + this.getDono());
}
else {
System.out.println("Não podemos cobrar, problemas na conta!");
}
}
public ContaBanco() {
this.setSaldo(0);
this.setStatus(false);
}
public int getNumConta() {
return numConta;
}
public void setNumConta(int numConta) {
this.numConta = numConta;
}
public String getTipo() {
return tipo;
}
public void setTipo(String tipo) {
this.tipo = tipo;
}
public String getDono() {
return dono;
}
public void setDono(String dono) {
this.dono = dono;
}
public float getSaldo() {
return saldo;
}
public void setSaldo(float saldo) {
this.saldo = saldo;
}
public boolean getStatus() {
return status;
}
public void setStatus(boolean status) {
this.status = status;
}
}
PS: Prefira usar métodos setter do que mexer direto nos atributos.
Com esse código pronto, vá na classe principal e coloque esses comandos pra criar os objetos:
package bancopoo;
public class BancoPoo {
public static void main(String[] args) {
ContaBanco p1 = new ContaBanco();
ContaBanco p2 = new ContaBanco();
p1.abrirConta("CC");
p1.setNumConta(1111);
p1.setDono("Jubileu");
p2.abrirConta("CP");
p2.setNumConta(2222);
p2.setDono("Creuza");
p1.estadoAtual();
p2.estadoAtual();
}
}
Para depositar, basta colocar isso, no final do código, antes da função estadoAtual():
p1.depositar(300);
p2.depositar(500);
Para sacar, da mesma forma:
p2.sacar(100);
E cobrar mensalidade também:
p1.pagarMensal();
p2.pagarMensal();
Tente outros números, principalmente na parte de sacar. E tente as outras funções.
Paralelamente, podemos fazer um exemplo apenas com métodos e atributos estáticos:
public class Ventilador {
private static boolean ligado = false;
private static int velocidade = 0;
public static void ligar(int vel) {
ligado = true;
if(vel < 1) {
vel = 1;
}
else if(vel > 3) {
vel = 3;
}
velocidade = vel;
System.out.printf("O ventilador está ligado, na velocidade de %d.\n", velocidade);
}
public static void desligar() {
velocidade = 0;
ligado = false;
System.out.println("O ventilador está desligado!");
}
public static boolean getLigado() {
return ligado;
}
public static int getVelocidade() {
return velocidade;
}
public static void setLigado(boolean ligado) {
Ventilador.ligado = ligado;
}
public static void setVelocidade(int velocidade) {
Ventilador.velocidade = velocidade;
}
}
E na classe principal:
Ventilador.ligar(3);
Ventilador.ligar(2);
Ventilador.desligar();
System.out.println(Ventilador.getLigado());
Como sabemos, a POO tem três pilares, Encapsulamento, Herança e Polimorfismo (representados pelas letras EHP). O primeiro que vamos tratar é o encapsulamento.
PS: Alguns consideram a Abstração um dos pilares também, o primeiro deles, mas na verdade ele está dentro do encapsulamento.
Imagine que o carro, uma pilha ou qualquer outra coisa esteja numa "cápsula", no exemplo, vamos usar um controle remoto. O código estando encapsulado, ele está protegido internamente (como uma cápsula de remédio mesmo), deixando o que é pra ser privado "blindado" do usuário, e o que é público com contato permitido.
A pilha também é um ótimo exemplo de encapsulamento, onde os componentes químicos que geram a eletricidade estão protegidos do contato humano (para proteger tanto os componentes quanto a pessoa), mas as partes metálicas estão disponíveis para fornecer energia ao produto. Além disso, a pilha sempre tem um formato padrão (tipo o AAA) para servir em vários produtos, independente da funcionalidade do produto. Mesmo se o componente interno da pilha for diferente (alcalina, comum, recarregável, etc.).
No exemplo que usaremos, o controle remoto, a "cápsula" seria a capa que permite que nós interaja com ele, e o código seria os circuitos que operam isso, mas que não podemos tocar diretamente por segurança.
O código "encapsulado" tem o mesmo padrão, e protege o código do usuário e vice-versa, impedindo que o programador cometa erros e prejudique o programa como um todo, independente do código encapsulado (ele só verificará as informações necessárias, sem precisar saber como ele funciona exatamente).
Em outras palavras, encapsular é ocultar partes independentes da implementação, permitindo construir partes invisíveis ao mundo exterior.
Basicamente, o uso de atributos privados e o acesso a eles ser feito através de métodos (como os getters e setters), já é o encapsulamento. Mas podemos fazer isso a nível de classe, usando uma interface, que obrigará a classe a ter todos os métodos descritos nela, além de proteger os atributos da classe implementadora do programa principal.
Para criar um exemplo disso com um controle remoto, temos que definir uma interface (seria a "cápsula" do controle com os botões), de forma parecida com uma classe, mas sem atributos, e todos os métodos são públicos. E a classe ControleRemoto (como os circuitos encapsulados pela interface) terá os atributos e os métodos getters e setters privados (é o passo principal pra encapsular). Veja o que faremos no Java:
<<interface>> Controlador |
---|
+ ligar() + desligar() + abrirMenu() + fecharMenu() + maisVolume() + menosVolume() + ligarMudo() + desligarMudo() + play() + pause() |
ControleRemoto |
---|
- volume - ligado - tocando |
(Receberá elementos do controlador) - setVolume(volume) - getVolume() - setLigado(ligado) - getLigado() - setTocando(tocando) - getTocando() |
Crie um novo projeto Java com classe principal, e nele crie uma interface com o nome Controlador, esse é o código dele:
package encapsulamentocontrole;
public interface Controlador {
public void ligar();
public void desligar();
public void abrirMenu();
public void fecharMenu();
public void maisVolume();
public void menosVolume();
public void ligarMudo();
public void desligarMudo();
public void play();
public void pause();
}
PS: Nem sempre é void, pode ser o tipo de retorno, caso exista. E os métodos abstratos significam que o método não será desenvolvido na interface, e sim na classe. É como um aviso pra interface que existe um método (por exemplo, de abrir, aumentar volume, etc.), mas a interface não precisa e nem sabe como o código funciona na classe, apenas esta que tem que implementar (executar). Se um método a ser implementado numa classe for estático, ele não pode ser indicado na interface. Interfaces agem de forma parecida com uma classe abstrata, só que sem implementações e sem atributos, apenas a assinatura dos métodos, que são abstratos por padrão, dispensando a indicação de abstract
neles.
Crie também a classe ControleRemoto, assim (os atributos deverão estar privados, os métodos getter e setter não são obrigatórios ser):
package encapsulamentocontrole;
public class ControleRemoto implements Controlador {
private int volume;
private boolean ligado;
private boolean tocando;
public ControleRemoto() {
this.volume = 50;
this.ligado = false;
this.tocando = false;
}
@Override
public void ligar() {
this.setLigado(true);
}
@Override
public void desligar() {
this.setLigado(false);
}
@Override
public void abrirMenu() {
System.out.println("Está Ligado? " + this.getLigado());
System.out.println("Está Tocando? " + this.getTocando());
System.out.print("Volume: " + this.getVolume());
for(int i = 0; i < this.getVolume(); i += 2) {
System.out.print("|");
}
System.out.println("");
}
@Override
public void fecharMenu() {
System.out.println("Fechando Menu...");
}
@Override
public void maisVolume() {
if(this.getLigado()) {
if(this.getVolume() < 100) {
this.setVolume(this.getVolume() + 2);
}
}
else {
System.out.println("Erro! Está Desligado, Não Podemos Aumentar o Volume!");
}
}
@Override
public void menosVolume() {
if(this.getLigado()) {
if(this.getVolume() > 0) {
this.setVolume(this.getVolume() - 2);
}
}
else {
System.out.println("Erro! Está Desligado, Não Podemos Diminuir o Volume!");
}
}
@Override
public void ligarMudo() {
if(this.getLigado() && this.getVolume() > 0) {
this.setVolume(0);
}
}
@Override
public void desligarMudo() {
if(this.getLigado() && this.getVolume() == 0) {
this.setVolume(50);
}
}
@Override
public void play() {
if(this.getLigado() && !(this.getTocando())) {
this.setTocando(true);
}
}
@Override
public void pause() {
if(this.getLigado() && this.getTocando()) {
this.setTocando(false);
}
}
private int getVolume() {
return volume;
}
private void setVolume(int volume) {
this.volume = volume;
}
private boolean getLigado() {
return ligado;
}
private void setLigado(boolean ligado) {
this.ligado = ligado;
}
private boolean getTocando() {
return tocando;
}
private void setTocando(boolean tocando) {
this.tocando = tocando;
}
}
PS: Usamos implements
quando precisamos implementar uma interface, sendo obrigado executar todos os métodos descritos nela, é possível uma classe implementar mais de uma interface, com os nomes separados por vírgulas, usando algo como public class NomeDaClasse implements NomeDaInterface1, NomeDaInterface2
.
Observe que no Java, ele coloca @Override
para indicar que o método criado na interface será sobreescrito na classe, em algumas linguagens POO, como o PHP, ele não existe.
E para criar o objeto controle, fazemos assim na classe principal:
package encapsulamentocontrole;
public class EncapsulamentoControle {
public static void main(String[] args) {
Controlador c = new ControleRemoto(); // O "tipo" é o nome da interface e a instância é a classe que a implementa
c.ligar();
c.maisVolume(); // Aqui podemos colocar os outros métodos.
c.abrirMenu();
}
}
Como exemplo, crie outros exemplos e funções baseados nesse controle.
Essa é a vantagem do encapsulamento, ele protege o código contra ações externas e internas, e torna a forma interna de implementação invisível pro "lado de fora" (sabemos que quem mexe nela sabe como usar as funções, mas não como funcionam, até os métodos getter e setter são privados por isso).
Em outras palavras, as interfaces estabelecem contratos entre as classes que as implementam, garantindo que elas tenham um certo comportamento. Por isso, podemos declarar um objeto do tipo da interface e iniciar com a classe que a implementa.
As interfaces também garantem o encapsulamento, já que podemos usar somente os métodos descritos na interface nos objetos, e não permitir a visualização de atributos da classe implementadora.
PS: Podemos verificar se uma classe implementa uma interface usando um if e else, isso também vale pra classes:
Controlador c = new ControleRemoto(); // O "tipo" é o nome da interface e a instância é a classe que a implementa
if(c instanceof Controlador) {
c.ligar();
c.maisVolume(); // Aqui podemos colocar os outros métodos.
c.abrirMenu();
}
else {
System.err.println("O objeto não implementa a interface Controlador!");
}