Polimorfismo

Padrão

Olá pessoal! Hoje encerramos a nossa série sobre orientação a objetos… sim, como eu disse lá no primeiro post, esta série é bem mais curta que a nossa primeira, sobre Programação para Iniciantes. Mas acredito que ao longo destes 5 posts, foi possível pelo menos dar uma noção sobre orientação a objetos. Obviamente, não tratei de conceitos complexos (afinal eu nem sou autoridade no assunto pra abordar tópicos muito avançados).

Então, sem mais delongas, vamos falar sobre o tal do Polimorfismo. Sim… um nome “feio”, mas nem por isso é complicado. Praticamente, todo o conceito que envolve o polimorfismo já foi visto no último post, sobre métodos e classes abstratas.

Mas o que vem a ser o polimorfismo (polymorphism, em inglês) é uma técnica que permite que referências de tipos de classes mais abstratas representem o comportamento das classes concretas que referenciam. Dessa forma, é possível tratar vários tipos de maneira homogênea (através da interface do tipo mais abstrato).

Continue lendo!

Classes e Métodos Abstratos

Padrão

Uma Classe Abstrata é desenvolvida para representar entidades e conceitos abstratos. A classe abstrata é sempre uma superclasse que não possui instâncias (ou melhor, não pode ser instanciada). Ela define um modelo para determinada funcionalidade e geralmente fornece uma implementação incompleta – a parte genérica – dessa funcionalidade. Caso a classe abstrata não possua nenhuma implementação (possuindo apenas as assinaturas dos métodos), ela também pode ser chamada de Interface. Cada uma das classes derivadas da classe abstrata completa a funcionalidade da classe abstrata, adicionando um comportamento específico.

Geralmente, uma classe abstrata possui métodos abstratos. Esses métodos são implementados em suas subclasses com o objetivo de definir seu comportamento específico. O método abstrato define apenas a assinatura do método e, portanto, não tem código.

No caso do Java, em específico, ele diferencia uma classe abstrata de uma interface. Vamos ver no exemplo a seguir, como ficaria a implementação de uma classe abstrata em C++…

#include <iostream>
using namespace std;

class Animal
{
public:
    virtual void comer() = 0;
    // Superclasses devem ter o destrutor virtual
    virtual ~Animal()
    {
    }
};

class Lobo: public Animal
{
public:
    void comer()
    {
        cout << "Eu me alimento como um lobo!" << endl;
    }
};

class Peixe: public Animal
{
public:
    void comer()
    {
        cout << "Eu me alimento como um peixe!" << endl;
    }
};

int main(void)
{
    Animal *a = new Lobo();
    Animal *b = new Peixe();

    a->comer();
    b->comer();

    return 0;
}

…e em Java…

package teste;

abstract class Animal {

    public abstract void comer();
}

class Lobo extends Animal {

    @Override
    public void comer() {
        System.out.println("Eu me alimento como um lobo!");
    }
}

class Peixe extends Animal {

    @Override
    public void comer() {
        System.out.println("Eu me alimento como um peixe!");
    }
}

public class Teste {

    public static void main(String[] args) {
        Animal a = new Lobo();
        Animal b = new Peixe();

        a.comer();
        b.comer();
    }
}

Percebam, primeiramente, que em C++ a declaração da classe abstrata não ocorre explicitamente. Ela se torna abstrata pelo fato de possuir métodos sem implementação. Se declararmos um método em C++ como virtual e o implementarmos, fazemos com que a implementação dele por parte da subclasse torne-se opcional (o que não ocorre neste caso).

Ao herdarmos nas classes Lobo e Peixe as características da classe Animal, estamos automaticamente obrigando-as a implementar o método abstrato comer(), específico para cada uma das subclasses. Enquanto em C++ declaramos o método como virtual e colocamos um = 0 após a sua declaração, em Java precisamos especificar explicitamente que tanto a classe quanto o método são abstratos, através da palavra-chave abstract. Na hora de implementarmos em C++, não é necessário nada adicional, enquanto que em Java, é necessário adicionar a anotação @Override.

Apesar de não podermos instanciar a classe abstrata, podemos declará-la como um tipo, que aceitará qualquer uma de suas implementações (como ocorre nos exemplos). Porém, nesse caso, se as subclasses tiverem métodos específicos que não pertencem ao escopo da superclasse, eles não serão reconhecidos.

Em Java, ainda temos uma outra característica importante nesse sentido: o conceito de Interfaces. Na verdade, uma interface nada mais é que uma classe abstrata que possui somente assinaturas de métodos. Assim, o nosso exemplo acima ficaria da seguinte forma:

package teste;

interface Animal {

    public void comer();
}

class Lobo implements Animal {

    @Override
    public void comer() {
        System.out.println("Eu me alimento como um lobo!");
    }
}

class Peixe implements Animal {

    @Override
    public void comer() {
        System.out.println("Eu me alimento como um peixe!");
    }
}

public class Teste {

    public static void main(String[] args) {
        Animal a = new Lobo();
        Animal b = new Peixe();

        a.comer();
        b.comer();
    }
}

Se analisarmos nosso exemplo, o uso de interface na definição da classe Animal seria a alternativa mais correta. Percebam a diferença: não utilizamos a palavra-chave class, e os métodos não precisam da palavra abstract, já que isso já está implícito no conceito da interface. Além disso, ao herdarmos da interface, utilizamos a palavra implements em vez de extends.

Isso me remete a uma coisa interessante: ao contrário do C++, a linguagem Java não permite o uso de herança múltipla, ou seja, uma classe só pode estender uma outra classe. Porém, ela pode implementar diversas interfaces. Assim:

class SubClasse extends ClassA implements ClasseB {
...

Bom pessoal, é isso. No próximo post desta série sobre Orientação a Objetos, veremos o conceito de polimorfismo. Estudem o assunto deste post de hoje, pois ele é a base para o entendimento do próximo post.

Herança

Padrão

Olá leitores! Apesar de meio parada, a nossa série de posts sobre Orientação a Objetos continua! Hoje iremos tratar de um assunto de extrema importância dentro das técnicas de modelagem de sistemas orientados a objetos: os conceitos de Herança (ou Inheritance). Mas o que vem a ser herança?

A grosso modo, podemos dizer que programar utilizando herança é reaproveitar código que será comum a outras classes. Utilizar herança é descobrir semelhanças entre classes.

Herança lembra família… é o que é transmitido de uma geração para outra…

Projetar utilizando herança significa colocar código comum em uma classe e dizer às classes mais específicas que a classe comum (mais abstrata) é sua superclasse. Quando uma classe herda de outra, a subclasse herda da superclasse. A notação de herança em C++ é…

class SubClasse: public SuperClasse {
...
};

… e em Java…

class SubClasse extends SuperClasse {
...
}

Voltando aos conceitos, dizemos que um relacionamento de herança significa que a subclasse herdará os membros da superclasse. Os “membros de uma classe” são os seus métodos e suas variáveis de instância (ou atributos). Vamos a um exemplo prático. Temos uma classe chamada Médico que iremos especializar em Clínico Geral e Cirurgião. Na notação UML (Unified Modeling Language), teríamos algo assim:

Esta é a notação de herança na UML. A classe mãe (ou superclasse) ligada a suas filhas (subclasses) por uma linha, com um triângulo indicando a presença de herança. Bom, neste caso, temos a classe mais abstrata, chamada Médico que possui uma variável de instância chamada trabalhaNoHospital e um método chamado tratarPaciente(). As duas subclasses, Clínico Geral e Cirurgião, automaticamente possuem estes atributos em seu escopo. No caso do Clínico Geral, ele possui, adicionalmente, uma variável de instância chamada atendeEmCasa e um método receitar(), que são específicos dele. Já a classe Cirurgião sobrescreve o método tratarPaciente() e adiciona o método fazerIncisão().

Mas espere aí? O que significa sobrescrever um método?

No nosso exemplo, a superclasse Médico tem o seu método tratarPaciente(), pois é uma ação comum a todo médico. No caso da superclasse, todo médico faz um check-up em seu paciente. Esse é o comportamento comum. A classe Clínico Geral possui esse comportamento, ou seja, um médico clínico geral, ao tratar o paciente, realiza um check-up nele. Já no caso da classe Cirurgião, ele também trata o paciente. Porém, o seu tratamento consiste em realizar a cirurgia no tratamento. Apesar de ter o mesmo método, o seu comportamento é diferente da classe mãe. Assim, ela sobrescreve o método da superclasse, adicionando seu comportamento próprio.

Vamos ver, então, como ficaria isso nas linguagens C++…

class Medico
{
    bool trabalhaNoHospital;

    void tratarPaciente()
    {
        // faz um check-up
    }
};
class ClinicoGeral: public Medico
{
    bool atendeEmCasa;

    void receitar()
    {
        // para tratar problemas simples
    }
};
class Cirurgiao: public Medico
{
    void tratarPaciente()
    {
        // realizar cirurgia
    }

    void fazerIncisao()
    {
        // faz a incisão (corta o paciente!)
    }
};

… e Java…

public class Medico {

    boolean trabalhaNoHospital;

    void tratarPaciente() {
        // faz um check-up
    }
}
public class ClinicoGeral extends Medico {

    boolean atendeEmCasa;

    void receitar() {
        // para tratar problemas simples
    }
}
public class Cirurgiao extends Medico {

    void tratarPaciente() {
        // realizar cirurgia
    }

    void fazerIncisao() {
        // faz a incisão (corta o paciente!)
    }
}

Como podem perceber, a sintaxe para a realização de herança nas linguagens é bastante simples, tanto em C++, quanto em Java.

Para não estender muito o post e evitar deixá-lo demasiadamente complexo, vamos encerrando por aqui. Recomendo que, para um melhor aprendizado sobre o assunto, vocês devam procurar material extra, tentar implementar pequenas estruturas de herança, enfim, realmente praticarem. Só através da prática é que conseguirão amadurecer os conceitos e conseguirão englobá-los no desenvolvimento de seus aplicativos.

No próximo post desta série, falaremos sobre o conceito de classes e métodos abstratos, assunto bastante interessante na Orientação a Objetos.

Até lá!

Encapsulamento de Dados

Padrão

Olá! Continuando a nossa série sobre orientação a objetos, vamos falar hoje sobre Encapsulamento de Dados. Na Orientação a objetos, o encapsulamento significa separar o programa em partes, o mais isoladas possível. Dessa forma, tem-se por objetivo tornar o software mais flexível, fácil de modificar e de criar novas implementações.

No encapsulamento de dados, o que fazemos é impedir o acesso direto aos atributos da classe, criando métodos tanto para obter os valores dos atributos, quanto para atribuir valores a eles. Os métodos que realizam tais tarefas são comumente chamaos de setters e getters (dos verbos set e get, em inglês).

Dessa forma, a classe Cachorro que tínhamos em nosso primeiro post sobre OO, utilizando o encapsulamento de dados, ficaria assim:

Primeiro em C++…

// Arquivo cachorro.h
#include <iostream>
#include <string>
using namespace std;

class Cachorro {
private:
    int tamanho;
    string raca;
    string nome;
public:
    Cachorro();
    ~Cachorro();
    void setTamanho(int tamanho);
    int getTamanho();
    void setRaca(string raca);
    string getRaca();
    void setNome(string nome);
    string getNome();
    void latir();
};
// Arquivo cachorro.cpp
#include "cachorro.h"

Cachorro::Cachorro() {
}

Cachorro::~Cachorro() {
}

void Cachorro::setTamanho(int tamanho) {
    this->tamanho = tamanho;
}

int Cachorro::getTamanho() {
    return tamanho;
}

void Cachorro::setRaca(string raca) {
    this->raca = raca;
}

string Cachorro::getRaca() {
    return raca;
}

void Cachorro::setNome(string nome) {
    this->nome = nome;
}

string Cachorro::getNome() {
    return nome;
}

void Cachorro::latir() {
    cout << "Ruff! Ruff!" << endl;
}

… e depois em Java…

// Arquivo Cachorro.java
package canil.exemplo;

public class Cachorro {
    private int tamanho;
    private String raca;
    private String nome;

    public void setTamanho(int tamanho) {
        this.tamanho = tamanho;
    }

    public int getTamanho() {
        return tamanho;
    }

    public void setRaca(String raca) {
        this.raca = raca;
    }

    public String getRaca() {
        return raca;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public String getNome() {
        return nome;
    }

    public void latir() {
        System.out.println("Ruff! Ruff!");
    }
}

Uma das grandes vantagens do uso do encapsulamento de dados é controlar o acesso aos atributos da classe, evitando comportamentos inesperados. Por exemplo, se determinado atributo não pode ser negativo, você pode condicionar o seu método de atribuição (set) a não deixar valores negativos (não atribuindo nada ou atribuindo alguma valor válido padrão). Além disso, caso determinado valor deva ser privado e não deve possuir acesso externo à classe, basta não definir seus métodos de acesso.

Bom pessoal, é isso. Aparentemente um assunto simples, mas onipresente no projeto de sistemas utilizando a orientação a objetos. No próximo post da série, falaremos sobre outro conceito muito importante na OO: a Herança!

Até lá!

Introdução à Orientação a Objetos

Padrão


Olá pessoal! Depois da nossa série de posts para programadores iniciantes, vamos começar aqui uma nova série, avançando um pouco mais no mundo da programação. Nesta série, falaremos sobre os principais conceitos da Orientação a Objetos, o paradigma mais utilizado hoje no mercado. Esta série (bem mais curta que a outra), servirá de base para que possamos seguir em frente em nossos estudos, para desenvolvermos aplicações Web (que está planejado para ser uma terceira série de posts). Na orientação a objetos, normalmente não se usa a representação algorítmica, como na programação estruturada, mas sim a representação na forma de diagramas, utilizando a Unified Modeling Language (UML). Para as partes práticas, pretendo demonstrar os conceitos utilizando as linguagens C++ e Java. Para o desenvolvimento dos aplicativos em C++ recomendo o Code::Blocks (o mesmo que utilizávamos para escrever códigos em C) ou o Visual Studio Express e para Java recomendo o Eclipse ou o NetBeans. Lembrando que, para o desenvolvimento de aplicações em Java, é necessário instalar o Java Development Kit (JDK), que pode ser baixado no site da Oracle. Todas as ferramentas citadas aqui são gratuitas e, com exceção do Visual Studio, estão disponíveis tanto para Windows, quanto para Linux e Mac. Em um momento oportuno, posso escrever um post a respeito da utilização de algumas dessas IDEs.

Bom, então vamos começar a falar sobre a Orientação a Objetos. 🙂

A orientação a objetos (OO) é um paradigma (uma espécie de fórmula, ou melhor, metodologia) de análise, projeto e programação de sistemas de software. O termo foi criado por Alan Kay, criador da linguagem Smalltalk. Hoje em dia, grande parte das linguagens utilizam esse paradigma, como Java, C#, C++, Object Pascal (Delphi), Ruby, Python, PHP (a partir da versão 4) etc. Mas não vamos nos prender muito a história e vamos entender o que vem a ser a OO.

Antigamente, o objetivo principal dos desenvolvedores era otimizar o código, principalmente pelo motivo de que o hardware na época era um tanto quanto limitado com relação aos recursos. Esse tipo de desenvolvimento, muitas vezes melhorava o aplicativo, em detrimento ao desenvolvimento, que era mais trabalhoso. Nessa época, o paradigma estruturado era o que dominava o mercado (o tipo de desenvolvimento que vimos na primeira série de posts.) Com o tempo, viu-se a necessidade de uma nova forma de desenvolver software. Queria-se um nível de abstração maior, de forma que o planejamento e o desenvolvimento do software se assemelhasse com o mundo real. No mundo real, interagimos com as coisas. E cada uma dessas “coisas” que interagimos, tem características e fazem certas ações. Assim, essas “coisas” foram chamadas de objetos. Passou-se então a pensar em objetos interagindo no software. Surgia assim a Orientação a Objetos.

Ao projetar sistemas utilizando a OO, identificamos as entidades, que são essas “coisas” que comporão o nosso software. Essas entidades, ao passarmos para a programação, essas entidades se tornarão as classes. As classes tem, basicamente, duas partes principais. A primeira dela, é a parte de características. É nessa parte que temos as informações a respeito da classe. Por exemplo, uma classe chamada Pessoa. O que temos com relação às características? O nome, a data de nascimento, o tipo sanguíneo, etc. Na programação, essas características são conhecidas como variáveis de instância. A segunda parte é a parte que define o comportamento da classe. Ainda seguindo o exemplo de uma classe chamada Pessoa, teríamos ações como andar, falar, bater o coração (por que não?), respirar, etc. Na programação, as coisas que determinada classe faz são chamadas de métodos. Então, podemos dizer que as classes são compostas de variáveis de instância e métodos.

Tá bom… eu to falando aqui de classes e tal… mas e os objetos? Afinal é orientação a objetos, não é? Sim sim, claro. O que acontece é que não trabalhamos diretamente com as classes, e sim com os objetos. As classes são usadas para construir os objetos. Uma classe é o projeto de um objeto. Ela diz como o objeto deve ser criado. É como se fosse o tipo do objeto (tipo no conceito de tipos na programação). Vamos pensar em um exemplo. No meu programa tenho uma classe Botão. A classe Botão será usada para criar vários botões (cada um, um objeto), cada qual com suas próprias características. Como assim? Mas as características não são definidas na classe? Mais ou menos. Continuando com o exemplo do Botão. Podemos definir na classe que um botão terá características como cor, tamanho, forma, rótulo. Cada um dos objetos do tipo botão, terá suas próprias características: sua própria cor, seu próprio tamanho, etc.

Antes de eu mostrar como funciona isso na prática, apenas vou fazer algumas breves explanações a respeito das linguagens que iremos utilizar para ilustrar a OO. Em C++, que foi derivado do C, temos também a função main() (derivada do paradigma estruturado) que define o ponto onde o programa é inicializado. Já no Java, não temos funções. Tudo nele é orientado a objetos, de forma que não temos funções. Um programa é executado através de um método de uma classe, que também deve ser chamado de main. Ao executarmos um programa, indicamos a classe que possui tal método e então, a partir dele, é iniciada a execução.

Vamos pegar como exemplo a classe Cachorro. Vamos criar essa classe e definir objetos a partir dela. Percebam que a sintaxe das duas linguagens são bastante parecidas com a linguagem C. O conjunto de linguagens com esse estilo de sintaxe é conhecida como linguagens C-Like. Mas logo em seguida, vou explicar alguns elementos principais da linguagem.

Primeiro em C++…

// Arquivo cachorro.h

#include <iostream>
#include <string>
using namespace std;

class Cachorro {
public:
    int tamanho;
    string raca;
    string nome;

    Cachorro();
    ~Cachorro();
    void latir();
};

 

// Arquivo cachorro.cpp

#include <iostream>
using namespace std;
#include "cachorro.h"

Cachorro::Cachorro() {
}

Cachorro::~Cachorro() {
}

void Cachorro::latir() {
   cout << "Ruff! Ruff!" << endl;
}

 

// Arquivo main.cpp

#include <iostream>
using namespace std;
#include "cachorro.h"

int main(void) {
    Cachorro cachorro;
    cachorro.tamanho = 40;
    cachorro.raca = "Doberman";
    cachorro.nome = "Rex"; // Muito criativo, não?

    cachorro.latir();

    return 0;
}

… e depois em Java…

// Arquivo Cachorro.java

package canil.exemplo

public class Cachorro {
    public int tamanho;
    public String raca;
    public String nome;

    public void latir() {
        System.out.println("Ruff! Ruff!");
    }

    public static void main(String[] args) {
        Cachorro cachorro = new Cachorro();
        cachorro.tamanho = 40;
        cachorro.raca = "Doberman";
        cachorro.nome = "Rex";

        cachorro.latir();
    }
}

Em uma visão geral, no exemplo acima, criamos uma classe chamada Cachorro, com as variáveis de instância tamanho, raça e nome, além do método latir(). A partir dessa classe, criamos um objeto chamado cachorro, atribuímos valores às suas variáveis de instância e, em seguida, fizemos uma chamada ao método latir, que escreve “Ruff! Ruff!” na tela, pulando para a próxima linha logo em seguida. Vamos agora analisar como isso foi feito nas duas linguagens.

Primeiramente, vamos analisar o código-fonte na linguagem C++. Em ambas as linguagens, é convenção declararmos cada classe em um arquivo separado. No caso do C++, cada classe é composta de um arquivo .h (declaração) e um arquivo .cpp (implementação). No arquivo de cabeçalho, cachorro.h, Iniciamos com um comentário (da mesma forma como fazíamos em C), e em seguida incluímos alguns arquivos. O arquivo iostream é parecido com o stdio que utilizávamos em C. É ele que nos fornece os meios de realizarmos, por exemplo, uma escrita na tela ou uma leitura de teclado. Isso é feito através dos objetos cout e cin. Esses objetos são objetos de fluxo, ou seja, direcionamos conteúdo a eles, e eles o mostram na tela (no caso do cout). Veremos mais sobre objetos de fluxo no decorrer dos posts. Logo em seguida, incluímos o arquivo string, que possui a declaração de uma classe de mesmo nome. Em C, utilizávamos vetores do tipo char para representar strings. Já em C++, temos essa classe que nos fornece mais recursos com relação a esse tipo de dados. Essa classe resolve as questões relativas a alocação de memória, além de nos fornecer métodos para obtermos o tamanho da string, concatenar uma string com outra, obter porções da string, etc. Por isso, ao programar em C++, é sempre bom utilizar a classe string ao invés do tipo char*. Ah, e se precisar, por algum motivo, do vetor de caracteres, a classe string pode lhe fornecer o vetor correspondente através de um método chamado c_str(). Mas não vamos nos preocupar com isso agora. Logo após os includes, temos using namespace std. O uso de namespaces no C++ serve, dentre outras coisas, para que possamos ter classes de mesmo nome no programa. Como assim? É uma coisa que acontece mais em projetos maiores, mas por exemplo, eu posso ter duas classes cachorros. Uma no namespace canil e outra no namespace petShop… pode parecer meio inútil agora, mas em alguns casos mais específicos pode ser a solução. Nessa linha do código, dizemos que estamos utilizando o namespace de nome std em todo o código desse arquivo. Isso nos poupa de ficar digitando o namespace, por exemplo, em std::cout, std::string, etc. Em seguida começamos a declaração propriamente dita da nossa classe. A palavra-chave class seguida do nome da classe. Por convenção, colocamos a primeira letra em maiúsculo no nome da classe. Iniciamos então o escopo da classe com as chaves. Perceba que ao final da classe, deve-se colocar ; da mesma forma que fazíamos nas structs em C. Temos logo no início da classe o label public. Isso significa que tudo que vier após esse label poderá ser acessado de fora da classe. Não se preocupe muito com isso agora. Veremos mais a fundo isso no próximo post sobre encapsulamento. Temos, inicialmente a declaração das variáveis de instância da nossa classe: o atributo tamanho do tipo inteiro, e os atributos raca e nome, que são objetos da classe string. Em seguida, temos a declaração do método Construtor e Destrutor da classe. Esses métodos, que são obrigatórios em C++, representam o que a classe fará ao ser inicializada e ao ser encerrada. Por exemplo, ao criar um objeto do tipo Cachorro, automaticamente será chamado o método construtor, Cachorro(). O método destrutor será chamado quando a execução sair do contexto onde a classe foi criada (por exemplo, no bloco entre chaves onde a classe foi declarada) ou quando ela for explicitamente desalocada (veremos isso mais adiante, quando virmos alocação dinâmica). Em seguida temos o método latir() que não recebe nada e não retorna nada. Perceba que métodos nada mais são que funções dentro da própria classe. Os métodos são funções correspondentes ao “tema” da classe. Por exemplo, o método latir() faz sentido dentro de uma classe Cachorro, mas não faria sentido numa classe chamada Vaca. Perceba que no arquivo de cabeçalho apenas declaramos os métodos – como se fossem os “protótipos” dos métodos -, a implementação vai no arquivo .cpp. O nosso arquivo cachorro.cpp começa com a inclusão dos arquivos de cabeçalho: o arquivo iostream, pois usaremos o objeto cout no método latir() e o arquivo cachorro.h que contém a declaração de nossa classe. Iniciamos então com o construtor e o destrutor. A sintaxe em C++ na hora de implementar os métodos é <retorno> NomeDaClasse::NomeDoMétodo(<Argumentos>) {}. Percebam que o construtor e o destrutor são diferentes, pois por padrão não possuem nem argumentos, nem valor de retorno – isso pode não acontecer (veremos isso mais a frente, quando falarmos sobre sobrecarga de operadores). Logo em seguida, temos a declaração do nosso método latir(). Simplesmente jogamos para o objeto cout (que escreve dados na tela) a string “Ruff! Ruff!” através do operador <<. Enviamos também uma quebra de linha, em C++ representada pelo endl. Poderíamos também, como em C, o caractere n para pularmos de linha. Por fim, no arquivo main.cpp temos a nossa já conhecida função main(). Nela, incluímos o arquivo cachorro.h que contém a declaração de nossa classe e o iostream. Criamos então um objeto chamado cachorro (reparem que a declaração de um objeto se assemelha muito a declaração de uma variável. Só que ao invés de um tipo primitivo, temos uma classe. O objeto é uma variável do tipo daquela classe). O acesso aos itens da classe se dá pelo operador . da mesma forma que fazíamos o acesso às structs em C (exceto quando a alocação for dinâmica. Aí, utilizamos o ->). Simplificadamente (bastante simplificadamente), uma classe é uma “struct com funções”. Para essa primeira parte, essa definição é suficiente. Assim, terminamos a explicação do código em C++. Quaisquer dúvidas, postem aqui nos comentários.

Bom, agora vamos explicar o código-fonte em Java. Em Java, como eu disse acima, a execução é inicializada a partir de um método main de uma classe (Java não tem funções ou procedimentos como C++). Dessa forma, declaramos o nosso método main dentro da própria classe Cachorro. Outra coisa é que em Java, toda a declaração da classe é feita em um único arquivo .java. Dessa forma, em nosso exemplo, temos apenas o arquivo Cachorro.java. Lembrando que o nome do arquivo deve coincidir com o nome da classe que ele contém. Bom, iniciamos o código com a declaração do pacote. Pacotes são uma alternativa para estruturar e organizar as classes em uma aplicação Java. Verificando-se externamente ao código, nada mais é que uma estrutura de pastas. Nesse nosso exemplo, na raiz do projeto teríamos uma pasta chamada canil, dentro dela outra pasta chamada exemplo e por fim o nosso arquivo Cachorro.java. Por convenção, os nomes de pacotes devem sempre ser em minúsculo. Por enquanto pode parecer besteira, mas é uma forma útil de organizar suas classes quando seus projetos começarem a crescer. Em seguida, temos a declaração da classe. Encontramos aqui algumas diferenças para o C++. A primeira delas é que a declaração da classe possui um classificador de acesso, no caso, o public. Em Java, se a classe não for definida como pública, ela somente poderá ser acessada dentro do próprio arquivo em que foi declarada. Geralmente, quando temos somente uma classe declarada no arquivo, ela é declarada como pública. Outra diferença para o C++ é que a definição do escopo da classe não termina com o ponto-e-vírgula! Fiquem atentos a isso para evitar erros de sintaxe. Em seguida, temos a declaração dos atributos da classe. No caso do Java, também deve-se ficar atento, pois os identificadores de acesso vem antes de cada um dos atributos ou métodos. Outro detalhe é que a declaração de strings é feita com letra maiúscula! Em Java, todas as classes tem seu nome em maiúsculo – e como String é uma classe, ela tem seu nome em maiúsculo! No método latir, o único comentário a ser ponderado é o método para a escrita de texto na tela. Como eu disse anteriormente, em Java tudo são classes. Assim, a biblioteca padrão define uma classe chamada System para representar o sistema. Porém, ela é definida como estática, ou seja, não é possível instanciá-la (criar objetos a partir dela). Dessa forma, acessamos seus métodos e atributos diretamente – no caso o atributo out que representa a saída de vídeo, e chamamos seu método println, que escreve uma linha na tela e pula para a próxima linha em seguida. Depois, temos a declaração do método main, que é o ponto inicial de nosso aplicativo. Sua declaração sempre deve ser assim, declarado como public static void (ou seja, método público, estático (não é necessário instanciar a classe para executá-lo) e não retorna nada), recebendo um vetor de objetos String como argumento (sim, a declaração de vetores no Java é um pouco diferente que o C++… veremos isso com mais detalhes quando for oportuno). Bom, no método main, declaramos um objeto do tipo Cachorro e o instanciamos com o uso do new. Em Java, tudo é referência (apesar de não existirem ponteiros). Assim, precisamos instanciar explicitamente nosso objeto, caso contrário ele permanecerá vazio (null). No mais, atribuímos valores às variáveis de instância da classe e chamamos o método latir(). E encerramos a explicação do código em Java.

Bom pessoal, nesta primeira parte, vimos o conceito de orientação a objetos, qual a diferença entre uma classe e um objeto, além de fazermos um exemplo bem básico, pra já irmos nos acostumando com as linguagens. Aguardem o próximo post, onde falaremos sobre um assunto muito importante na OO: Encapsulamento de Dados!

Até lá!