Entendendo orientação a objetos, parte 3

Estou de volta para continuar a série de artigos sobre POO. A partir de agora, começaremos a ver conceitos um pouco mais avançados. Gostaria de lembrar também, que algumas linguagens orientadas a objetos tem certas diferenças em sintaxe. Aqui vou me restringir à linguagem Java fazendo comentários com C++.

Em seqüência ao último capítulo, vamos continuar a entender a herança em POO. Como foi visto anteriormente herança é a propriedade de uma classe herdar informações (ex.: atributos e métodos) de uma outra.

Em alguns casos, uma classe pode herdar informações de uma ou mais classes simultaneamente. Vamos supor que tenhamos as classes Destruidor e Defensor com uma terceira classe Robo herdando das duas anteriores, neste exemplo nosso robô assumirá as características de Destruidor e Defensor ao mesmo tempo, herdando as características de duas classes. Esta capacidade de herdar várias classes, chama-se herança múltipla.

public class Destruidor {
 
    public Destruidor() {
    }
 
    public void atacar() {
        System.out.println("Atirar com fuzil.");
    }
 
}
 
public class Defensor {
 
    public Defensor() {
    }
 
    public void defender() {
        System.out.println("Defender-se com escudo.");
    }
 
}
 
/* Este código não compila!!! Apenas para fim didático */
public class Robo extends Defensor, Destruidor {
 
    public Robo() {
    }
 
    public static void main(String[] args) {
        Robo robo = new Robo();
        robo.atacar();
        robo.defender();
    }
 
}

Na linguagem C++ temos a possibilidade de herdar várias classes apenas especificando na declaração da classe, já na linguagem java não é possível fazer com que uma classe herde (estenda) mais de uma classe, então temos que contornar o problema utilizando outras técnicas em Java, vamos entender o porquê desta restrição.

De volta ao exemplo da classe sobre robôs, desta vez vamos supor que tenhamos as seguintes classes: Prototipo, Destruidor e AutoDestruidor, vamos analisar como seria o código utilizando a classe Robo para herdar as características das anteriores:

public class Prototipo {
 
    public Prototipo() {
    }
 
    public void atacar() {
        System.out.println("Atirar de pistola.");
    }
 
}
 
public class Destruidor {
 
    public Destruidor() {
    }
 
    public void atacar() {
        System.out.println("Atirar com fuzil.");
    }
 
}
 
public class AutoDestruidor {
 
    public AutoDestruidor() {
    }
 
    public void atacar() {
        System.out.println("Robô bomba, explodir!");
    }
}
 
/* Este código não compila!!! Apenas para fim didático */
public class Robo extends Destruidor, AutoDestruidor {
 
    public Robo() {
    }
 
    public static void main(String[] args) {
        Robo robo = new Robo();
        robo.atacar();
    }
 
}

E agora? Se eu mandar o robô atacar, ele atira de pistola, de fuzil ou ele se detona? Esse é o conhecido problema do diamante (desenhe a modelagem UML dessas classes e entenderá o porquê deste nome). O problema do diamante é o motivo especial pelo qual a linguagem Java não trabalha diretamente com herança múltipla. O que poderíamos fazer para contornar este problema? Utilizar interfaces, desta forma sempre teremos apenas uma implementação para cada método.

Agora que sabemos como funciona herança, podemos conhecer o terceiro e último modificador de encapsulamento, o protected, que assume private ou public de acordo com a classe a qual está sendo acessada, com ele especificamos que um atributo (ou método) é definido como private para subclasses (classes filhas herdadas) e as classes do mesmo pacote, enquanto fica definida como public para as demais classes.

O modificador final serve para indicar que é proibido modificações posteriores. Pode ser usado em classe, método e atributos, uma classe com o modificador final não poderá ser herdada por nenhuma outra classe, nem um método poderá ser sobrescrito tampouco poderá um atributo ter seu valor modificado depois de uma atribuição inicial.

Já o modificador static define que o atributo (ou método) será de acesso da classe e não do objeto. Qual a diferença disso? Se um método pertence ao objeto, ele somente poderá ser acessado após haver uma instância alocada da classe, se temos dois objetos, carroA e carroB, da classe Carro a qual possui um método correr, se fizermos uma chamada para o método correr no objeto carroA e no carroB, teremos duas chamadas do método correr ocorrendo em posições de memória diferentes onde seu resultado vai depender das variáveis particulares de carroA e carroB. Caso o modificador static fosse adicionado ao método correr, não seria necessário alocar nenhuma instância de Carro para que pudéssemos fazer uma chamada ao método correr, este método poderia ser chamado diretamente da classe Carro.

Também podemos utilizar o modificador static em atributos, isto seria propício para termos uma variável a qual todos os objetos pudessem conhecer o seu valor, tal fato ocorre devido a utilização do modificador static, ao invés de serem alocados n atributos para n objetos, é alocado apenas 1 atributo para n objetos, onde todos os objetos têm acesso ao mesmo atributo.

public class Carro {
 
    private String modelo;
    private static int numero;
 
    public Carro(String modelo) {
        this.modelo = modelo;
    }
 
    public void correr() {
        System.out.println(String.format("Estou correndo de %s. Fui o %dº a sair.", modelo, ++numero));
    }
 
    public static void frear() {
        System.out.println("Pisar no freio.");
    }
 
    public String getModelo() {
        return modelo;
    }
 
    public int getNumero() {
        return numero;
    }
 
    public static void main(String[] args) {
        Carro carroA = new Carro("Ferrari");
        Carro carroB = new Carro("Porsche");
 
        carroA.correr();
        carroB.correr();
 
        Carro.frear();
    }
 
}

Execução da classe Carro.java:

Estou correndo de Ferrari. Fui o 1º a sair.
Estou correndo de Porsche. Fui o 2º a sair.
Pisar no freio.

Entendendo Orientação a Objetos – Parte 2

Continuando nossa jornada no mundo da POO, vamos agora detalhar mais o conceito de classe, demonstrar exemplos específicos e iniciar a teoria sobre herança. Espero que, a partir da leitura da segunda parte deste artigo todos estejam capazes de implementar sua própria classe, então vamos ao trabalho!

Primeiramente vamos entender a estrutura de uma classe, a seguir temos um exemplo (em java) simples de uma classe Relogio que é capaz de imprimir um determinado horário.

/**  * Declaração da classe Relogio
     * @author paulocanedo */
public class Relogio {
 /** * Atributo privado do  tipo inteiro que guarda o valor do atributo hora  */
 private int hora;
 
 /** * Atributo privado do tipo inteiro que guarda o valor do atributo minuto */
 private int minuto;
 
 /** * Construtor da classe Relogio */
 public Relogio() { }
 
 /** * Método que retorna a hora e o minuto em uma String formatada no estilo hora:min
 * @return Hora e Minuto numa String formatada */
 public String getHoraMinuto() {
  return (String.format("%d:%d", hora, minuto));
 }
}

O método Relogio() tem o mesmo nome da classe por ser o método construtor, ou seja, quando uma classe é instanciada, imediatamente este método é chamado. Uma classe pode ter mais de um construtor, veremos isto um pouco mais tarde.

Os atributos hora e minuto estão com o modificador private, ou seja, estes atributos estarão visíveis apenas dentro do escopo da classe Relogio enquanto que o método getHoraMinuto(), possui o modificador public que indica que este método pode ser acessado por qualque classe. Mas… se os atributos hora e minuto possuem o modificador private e a função do método getHoraMinuto() (com modificador public) é de retornar uma string formatada contendo os atributos hora e minuto então pode haver um problema ao acessar este método? A resposta é não, isto ocorre porque o método getHoraMinuto() está na mesma classe que os atributos hora e minuto, em fim, o método getHoraMinuto() coleta as informações dos atributos e entrega para qualquer classe, agindo como uma espécie de intermediador.

Uma boa prática de programação é deixar todos os atributos de uma classe com o modificador private para dar segurança ao código. É uma forma de protegê-lo contra outros mal intencionados e até mesmo de protegê-lo contra você mesmo, sim isto é verdade, um programador destrói seu próprio código caso não tenha cuidado. Esta prática de esconder as coisas dentro da classe chama-se encapsulamento.
OK, mas uma hora eu vou precisar acessar um atributo através de outra classe, então para isso vamos utilizar os métodos chamados de get (pega o valor de um atributo) e set (define o valor de um atributo). Abaixo está um exemplo onde é mostrado a necessidade de trabalhar-se com get e set:

/**  * Declaração da classe Relogio  *
* @author paulocanedo */
public class Relogio {
 
 /** * Atributo privado do tipo inteiro que guarda o valor do atributo hora */
 private int hora;
 
 /** * Construtor da classe Relogio.
 * Atribue valor -1 ao atributo hora (indica que o valor é inválido).*/
 public Relogio() {
 this.hora = -1;
 }
 
 /** * Coleta as informações do atributo hora
  * @return Caso a hora seja válida retorna a hora contida no objeto caso contrário retorna -1 indicando valor inválido da hora.
  */
  public int getHora() {
   if(hora >= 0 && hora < = 24) {
    return hora;
   } else {
    return -1;
   }
  }
 
 /** * Define o valor da hora caso esta seja válida, caso contrário define valor -1 correspondente ao valor inválido
   * @param hora Valor inteiro correspondente a hora
   */
  public void setHora(int hora) {
   if(hora >= 0 && hora < = 24) {
    this.hora = hora;
  }
 }
}

Na declaração do método setHora(int), podemos notar a presença da palavra chave this. Esta palavra serve para explicitar que o acesso está sendo na própria classe a qual está trabalhando (e não a um escopo local). Na linha this.hora = hora indica que o atributo de classe hora está recebendo o valor da variável hora (recebida pela passagem de parâmetro). Generalizando o this obriga que o acesso seja no escopo de classe.

Outro problema que a POO veio resolver sobre a programação estruturada foi o conceito de reutilização de código. Uma das formas encontradas para sanar este problema é o uso de herança. Basicamente herança, computacionalmente falando, é a propriedade de uma classe herdar informações (ex.: atributos e métodos) de uma outra.

Mas onde usar herança? Só a experiência consegue ensinar um programador a descubrir quando usar herança, mas uma forcinha com exemplos ajuda bastante quando se está começando. Vamos supor que tenhamos que implementar uma classe para um quadrado e um retângulo. Sabemos que tanto o quadrado quanto o retângulo possuem as características de um quadrilátero, ou seja, eles herdam as características de um quadrilátero. Assim podemos ter a classe Quadrilatero sendo a classe pai e as classes Quadrado e Retangulo como suas classes filhas que herdam todas as propriedades de sua classe pai.

Para entender melhor o conceito de herança, tente pensar em coisas na natureza que sigam o conceito de herança, tente identificar as propriedades que podem ser herdadas de uma coisa para outra. Na próxima parte deste artigo, vou trazer um maior detalhamento sobre herança múltipla, modificadores static, final e protected, além de exemplos práticos para melhor compreensão. Obrigado a todos os leitores!