ejm-Orientação a Objetos - uma abordagem com Java - Parte 1

12
Orientação a Objetos: uma abordagem com Java – Parte 1 A essência da programação orientada a objetos usando Java Usando a linguagem de programação Java para aplicar as ideias, conceitos e abstrações do paradigma orientado a objetos CARLOS ARAÚJO Um parâmetro que pode ser usado para medir a complexidade de um software é o número de linhas de código. Por exemplo, o Windows Vista tem cerca de 50 milhões de linhas, o kernel do Linux 3.0 tem mais de 14 milhões de linhas, a versão 2.0 do OpenOffice já apresentava mais de 10 milhões de linhas de código. Por essa perspectiva não é difícil concluir o quanto a complexidade do software vem aumentando a cada ano que passa. Isso acarreta alguns problemas. Um deles é a dificuldade em cumprir o cronograma do projeto. Quanto maior o tamanho do programa, maior é a distância entre a estimativa de tempo prevista e o real. Segundo, os custos de desenvolvimento e manutenção aumentam consideravelmente com o tamanho do software. Estima-se, atualmente, que o custo de manutenção de um software atinge de 70 a 80% do custo total. Uma terceira dificuldade não é consequência da complexidade, mas devido à dinâmica dos negócios do usuário: os requisitos mudam. Neste século, os processos nos negócios mudam a cada seis meses ou menos. Nas décadas de 60 e 70, essas mudanças ocorriam a cada cinco anos. Uma das peças chave para solucionar essas dificuldades chama-se reutilização. A reutilização é fundamental para o aumento da produtividade e melhoria da qualidade. De maneira geral, reutilização consiste na utilização mais de uma vez de todos os tipos de informação e artefatos encontrados durante o processo de desenvolvimento, tais como requisitos, código e testes. Existem diversas técnicas que podemos lançar mão para conseguir a reutilização. Dentre elas podemos citar os repositórios dos sistemas de controle de versão e a orientação a objetos. E é sobre a orientação a objetos que pretendemos apresentar nesta matéria. A Programação Orientada a Objetos (Object-Oriented Programming) foi concebida na década de 60 no Centro Norueguês de Computação. Nessa época, os conceitos de classe e herança foram introduzidos através da linguagem Simula 67. No entanto, somente após o lançamento da linguagem Smalltalk nos anos 70 – considerada a linguagem orientada a objetos (OO) mais pura que existe – é que a OO começou a se popularizar. Após isso surgiram linguagens chamadas híbridas, tais como C++ e Java. Desta forma, neste artigo serão estudados os conceitos de orientação e de que forma podemos utilizar a linguagem Java para implementá-los. Pacotes Em geral, quando desenvolvemos pequenas aplicações, pode ser viável manter o código em um mesmo diretório. Entretanto, em aplicações maiores, colocar todos os arquivos em uma mesma pasta, sem organização, pode prejudicar principalmente a manutenção do sistema. Por isso sugere- se que o código seja agrupado, de forma que as classes relacionadas fiquem em um mesmo diretório. Esses diretórios – juntamente com os arquivos dentro deles – são chamados de pacotes, e tanto o código fonte das classes quanto os arquivos compilados – e mesmo outros pacotes – são organizados dentro desses diretórios. Por curiosidade, todo o código da API (Application Programming Interface) do Java também está organizado em pacotes. Por exemplo: o pacote java.io contém as classes referentes a I/O (Entrada/Saída) e o pacote java.net oferece o que é necessário para lidar com redes. 1/12

Transcript of ejm-Orientação a Objetos - uma abordagem com Java - Parte 1

Page 1: ejm-Orientação a Objetos - uma abordagem com Java - Parte 1

Orientação a Objetos: uma abordagem com Java – Parte 1A essência da programação orientada a objetos usando Java

Usando a linguagem de programação Java para aplicar as ideias, conceitos e abstrações do paradigma orientado a objetos

CARLOS ARAÚJO

Um parâmetro que pode ser usado para medir a complexidade de um software é o número de linhas de código. Por exemplo, o Windows Vista tem cerca de 50 milhões de linhas, o kernel do Linux 3.0 tem mais de 14 milhões de linhas, a versão 2.0 do OpenOffice já apresentava mais de 10 milhões de linhas de código.Por essa perspectiva não é difícil concluir o quanto a complexidade do software vem aumentando a

cada ano que passa. Isso acarreta alguns problemas. Um deles é a dificuldade em cumprir o cronograma do projeto. Quanto maior o tamanho do programa, maior é a distância entre a estimativa de tempo prevista e o real. Segundo, os custos de desenvolvimento e manutenção aumentam consideravelmente com o tamanho do software. Estima-se, atualmente, que o custo de manutenção de um software atinge de 70 a 80% do custo total. Uma terceira dificuldade não é consequência da complexidade, mas devido à dinâmica dos negócios do usuário: os requisitos mudam. Neste século, os processos nos negócios mudam a cada seis meses ou menos. Nas décadas de 60 e 70, essas mudanças ocorriam a cada cinco anos.Uma das peças chave para solucionar essas dificuldades chama-se reutilização. A reutilização é

fundamental para o aumento da produtividade e melhoria da qualidade. De maneira geral, reutilização consiste na utilização mais de uma vez de todos os tipos de informação e artefatos encontrados durante o processo de desenvolvimento, tais como requisitos, código e testes. Existem diversas técnicas que podemos lançar mão para conseguir a reutilização. Dentre elas podemos citar os repositórios dos sistemas de controle de versão e a orientação a objetos. E é sobre a orientação a objetos que pretendemos apresentar nesta matéria.A Programação Orientada a Objetos (Object-Oriented Programming) foi concebida na década de

60 no Centro Norueguês de Computação. Nessa época, os conceitos de classe e herança foram introduzidos através da linguagem Simula 67. No entanto, somente após o lançamento da linguagem Smalltalk nos anos 70 – considerada a linguagem orientada a objetos (OO) mais pura que existe – é que a OO começou a se popularizar. Após isso surgiram linguagens chamadas híbridas, tais como C++ e Java.Desta forma, neste artigo serão estudados os conceitos de orientação e de que forma podemos

utilizar a linguagem Java para implementá-los.

PacotesEm geral, quando desenvolvemos pequenas aplicações, pode ser viável manter o código em um

mesmo diretório. Entretanto, em aplicações maiores, colocar todos os arquivos em uma mesma pasta, sem organização, pode prejudicar principalmente a manutenção do sistema. Por isso sugere-se que o código seja agrupado, de forma que as classes relacionadas fiquem em um mesmo diretório. Esses diretórios – juntamente com os arquivos dentro deles – são chamados de pacotes, e tanto o código fonte das classes quanto os arquivos compilados – e mesmo outros pacotes – são organizados dentro desses diretórios. Por curiosidade, todo o código da API (Application

Programming Interface) do Java também está organizado em pacotes. Por exemplo: o pacote java.io contém as classes referentes a I/O (Entrada/Saída) e o pacote java.net oferece o que é necessário para lidar com redes.

1/12

Page 2: ejm-Orientação a Objetos - uma abordagem com Java - Parte 1

Supondo que uma classe chamada Pessoa pertença ao pacote br.com.nomeempresa.nomeprojeto, então seu nome completo será br.com.nomeempresa.nomeprojeto.Pessoa. Essa é uma forma de evitar conflitos de nomes, pois classes em pacotes diferentes têm nomes completos diferentes.Considerando este mesmo exemplo, quando o compilador encontra uma referência à classe Pessoa, ele irá

procurar o arquivo Pessoa.class no diretório br/com/nomeempresa/nomeprojeto. Tenha em mente que cada um desses diretórios é um pacote.Para indicar que um arquivo fonte Java pertence a um dado pacote, a primeira linha de código deste arquivo

deve ser a declaração:

package br.com.nomeempresa.nomeprojeto

No caso em que esta declaração não está presente, o arquivo fonte fará parte do pacote default, que é o próprio diretório corrente do projeto.Para referenciar uma classe de um pacote, é possível usar o nome completo da classe. No entanto, o que

se faz normalmente é usar a declaração import. Por exemplo, pode-se declarar o seguinte no início do código:

import br.com.nomeempresa.nomeprojeto.Pessoa;

A partir de então a classe pode ser referenciada apenas pelo nome Pessoa. Podemos também especificar no código do programa que todas as classes do pacote serão referenciadas somente pelo nome. Para obter esse efeito, declaramos:

import br.com.nomeempresa.nomeprojeto.*;

A exceção a essas regras é o uso das classes do pacote java.lang. Quando uma nova classe é criada na aplicação, implicitamente é declarado um import a este pacote. Ele contém classes essenciais para a interpretação de qualquer programa Java, tais como String, System e Object.

ClassesUm dos mais importantes produtos das fases da análise e projeto orientados a objetos é o modelo

de domínio da aplicação. Para representar esse modelo, vimos na segunda parte da série de artigos sobre Modelagem de Software com UML, publicada na Easy Java Magazine nº 5, que a Linguagem de Modelagem Unificada (UML) é uma das ferramentas que podemos adotar. A UML permite expressar o modelo de domínio em um diagrama de classes.Uma classe descreve um grupo de objetos com características e comportamento similares,

relacionamentos comuns com outros objetos e semântica comum. As características dos objetos são descritas pelos atributos e o comportamento é definido pelos métodos. Os métodos são equivalentes aos procedimentos e funções usados em linguagens do paradigma estruturado como Pascal e C, mas os métodos manipulam apenas suas variáveis locais e os atributos da classe.Na definição da classe, cada atributo é identificado por um nome e um tipo de dado. Em Java, o

tipo de dado de um atributo pode ser um tipo primitivo (int, char ou boolean, por exemplo) ou uma classe. Além de definir nome e tipo de dado do atributo, podemos estabelecer que ele irá receber um valor inicial – chamado valor default, ou padrão – no momento da criação do objeto. Para especificar que um atributo receberá um valor default, basta que seja atribuído a ele um valor na sua declaração, por exemplo:

double salario = 545.00;

Os atributos da definição de uma classe também são chamados variáveis, mas para distingui-las daquelas que são declaradas dentro de métodos, por exemplo, elas são chamadas variáveis de instância. A exceção ocorre com os atributos static – estudados daqui a pouco – que são chamados variáveis de classe.

2/12

Page 3: ejm-Orientação a Objetos - uma abordagem com Java - Parte 1

Vimos que, além das características, as classes definem o comportamento dos objetos, o qual é representado pelos métodos. Para declarar um método, é especificado o que chamamos de assinatura, que é constituída de um identificador – o nome do método – o tipo de dado para o retorno e a lista de argumentos. Cada argumento terá um nome que o identifica e um tipo de dado correspondente. Os argumentos são valores passados pelo objeto que chama o método, de forma que este possa cumprir o seu objetivo. Os argumentos são variáveis locais do método. A assinatura de um método com argumentos pode ser definida assim, por exemplo:

public double soma(double a, double b);

Se o método não tiver argumentos, devemos incluir parênteses vazios na declaração. A palavra-chave void deve ser declarada – no lugar do tipo de dado de retorno – caso o método não retorne nenhum valor. A seguir apresentamos um exemplo de declaração de um método sem argumentos e sem retorno:

public void imprimir();

Modificadores de acesso a membrosParece muito mais simples escrever programas que permitam o acesso às variáveis de instância de

um objeto. Porém, isso poderia resultar em um código pouco seguro. Por exemplo, suponha que qualquer objeto pudesse ter acesso ao atributo saldo de uma instância de ContaCorrente. Dessa forma, seria possível modificar o saldo de uma conta sem necessidade de fazer um depósito, por exemplo. Por essa razão, é recomendado restringir, tanto quanto possível, o acesso aos membros de um objeto. Essa é uma prática da orientação a objetos conhecida como encapsulamento, que estudaremos em seguida.De maneira a ajudar na tarefa de estabelecer esses limites, tanto os atributos quanto os métodos –

denominados membros de uma classe – podem ter modificadores de acesso, que podem ser:• public – os membros da classe – atributos e métodos – que recebem esse modificador podem

ser acessados por qualquer outro objeto;• private – neste caso os membros da classe não são acessíveis por qualquer outro objeto;• protected – membros que recebem esse modificador podem ser acessados fora do pacote

apenas por objetos de classes que derivem desta através de herança;• default – não é exatamente um modificador, mas sim um nível de acesso. Permite visibilidade

apenas dentro do pacote. Não exige um modificador explícito.

Considere então a classe Empregado, que tem os seguintes atributos com seus correspondentes tipos de dados: matricula – int, nome – String, cpf – int, cargo – String, salario – double e gratificacao – double. Suponha também que a classe tem o método calculaSalario() que retorna a soma de salario e gratificacao. Esta definição pode ser implementada preliminarmente conforme a Listagem 1.

Listagem 1. Código da classe Empregado.

public class Empregado {

int matricula;

String nome;

long cpf;

String cargo;

double salario;

double gratificacao;

public double calculaSalario() {

return this.salario + this.gratificacao;

}

}

EncapsulamentoOs atributos da classe Empregado, exibidos na Listagem 1, não possuem um modificador de

acesso, portanto têm nível de acesso default. Entretanto, essa não é uma boa prática da orientação a

3/12

Page 4: ejm-Orientação a Objetos - uma abordagem com Java - Parte 1

objetos, como falamos antes. A recomendação é que os atributos de um objeto e a implementação de seus métodos devem ser o mais privados possível. Em geral, os atributos não devem ser visíveis fora da classe e não se deve conhecer detalhes da implementação dos seus métodos. Essa prática recebe o nome de encapsulamento. Ou seja, devemos construir uma classe de forma que o usuário tenha acesso apenas ao seu conjunto de métodos – chamado de interface. Este conceito não deve ser confundido com o tipo abstrato interface, o qual define métodos que uma classe deve implementar, que será explicado na próxima parte da matéria. Por ora, podemos antecipar que o tipo interface é definido como se fosse uma classe sem atributos e cujos métodos possuem apenas a assinatura.Deste modo, vamos modificar o código da Listagem 1 para se adequar a esse conceito. Se você

estiver usando o NetBeans, selecione no menu Refatorar > Encapsular campos. Na janela que será aberta, pressione o botão Selecionar tudo. Na opção Javadoc você pode escolher se deseja comentários ou não. Deixe as demais opções com os valores padrão e pressione o botão Refatorar. Feito isso, será criado um código semelhante ao da Listagem 2.

Listagem 2. Código da classe Empregado com encapsulamento.

public class Empregado {

private int matricula;

private String nome;

private long cpf;

private String cargo;

private double salario;

private double gratificacao;

public double calculaSalario() {

return this.getSalario() + this.getGratificacao();

}

public int getMatricula() {

return matricula;

}

public void setMatricula(int matricula) {

this.matricula = matricula;

}

public String getNome() {

return nome;

}

public void setNome(String nome) {

this.nome = nome;

}

public long getCpf() {

return cpf;

}

public void setCpf(long cpf) {

this.cpf = cpf;

}

public String getCargo() {

return cargo;

}

public void setCargo(String cargo) {

this.cargo = cargo;

}

public double getSalario() {

return salario;

}

public void setSalario(double salario) {

this.salario = salario;

}

public double getGratificacao() {

return gratificacao;

}

4/12

Page 5: ejm-Orientação a Objetos - uma abordagem com Java - Parte 1

public void setGratificacao(double gratificacao) {

this.gratificacao = gratificacao;

}

}

Note que os modificadores de acesso dos atributos foram alterados para private. Além disso, foram criados os métodos getters e setters públicos para cada campo da classe. Os getters são métodos de leitura dos campos e os setters são os métodos de escrita. Esses métodos também são chamados de accessors e mutators, respectivamente. A palavra-chave this é usada para referenciar membros da instância corrente. Ou seja, this refere-se ao objeto que está executando o código onde esta palavra-chave pode ser vista. Esta referência não é obrigatória, porque se um método ou variável for acessado sem o operador ponto (.), isto significa que tal membro pertence à classe corrente. No entanto, a sua utilização torna o código mais fácil de ler.

Modificadores de classes Podemos observar que na declaração da classe utilizamos o modificador de acesso public, que

significa que a classe pode ser acessada por qualquer outra, independente do pacote onde cada uma esteja. Mas se não for especificado um modificador de acesso na declaração da classe, então ela terá acesso em nível de pacote, também chamado default, ou seja, a classe só poderá ser acessada por outra classe do mesmo pacote. Uma classe somente pode ter um desses dois níveis de acesso.Além do modificador public – ou a ausência dele – outros modificadores que não se referem a

níveis de acesso podem ser usados na declaração de uma classe, tais como final e abstract. O modificador final é usado para definir que a classe não pode ser estendida (herdada). Apesar de parecer estranho, em alguns casos isso pode ser necessário. Por exemplo, String é uma classe de Java definida como final. Imagine a confusão que seria se os programadores pudessem estender String e redefinir seu comportamento. Outro modificador que podemos usar para classes é abstract. Uma classe que tem esse modificador é chamada abstrata e não pode ser instanciada, ou seja, você não pode criar um objeto a partir dela. Mais adiante estudaremos herança e classes abstratas.

ConstrutoresEm Java, os objetos são construídos utilizando a palavra-chave new. Quando usamos este termo, é

executado um código que chamamos de construtor. Portanto, para que os objetos sejam instanciados, toda classe deve possuir, no mínimo, um construtor. Se o desenvolvedor não o criar explicitamente, o compilador cria um sem argumentos, que é denominado construtor padrão. Por exemplo, observe o código da Listagem 3.

Listagem 3. Código da classe Empregado com construtor explícito.

public class Empregado {

private int matricula;

private String nome;

private long cpf;

private String cargo;

private double salario;

private double gratificacao;

// construtor

public Empregado() {

}

public double calculaSalario() {

return this.getSalario() + this.getGratificacao();

}

//... restante do código aqui

}

Note que o construtor se assemelha a um método, com a diferença fundamental que um construtor não tem tipo de retorno. Eles devem ter exatamente o mesmo nome da classe onde são declarados e eventualmente podem ter os mesmos modificadores de acesso a membros. Na classe da Listagem 3

foi criado um construtor sem argumentos, que não deve ser chamado construtor padrão, mesmo se assemelhando ao que é criado pelo compilador. Pois, chamamos construtor padrão apenas aquele

5/12

Page 6: ejm-Orientação a Objetos - uma abordagem com Java - Parte 1

que o compilador cria implicitamente para nós, quando a classe não possui um construtor explicitamente declarado.Quando um construtor sem argumentos é usado para instanciar um objeto, e nenhum valor é

definido para inicialização dos atributos no corpo da classe, então o compilador define valores padrão para as variáveis de instância de acordo com o tipo de dado, conforme a Tabela 1. Isso obriga que tenhamos que inicializar os campos usando os métodos setters. Por isso, em geral, os construtores são usados para inicializar o estado das variáveis de instância. Na Listagem 4

mostramos o código da classe Empregado, implementando um construtor para inicializar os atributos.

Tipo de dado Valor da inicialização

byte, short, int, long 0

char ' '

boolean false

float 0.0f

double 0.0

String (ou qualquer outra classe) null

Tabela 1. Valores de inicialização padrão dos atributos de um objeto.

Listagem 4. Construtor para inicializar os atributos da classe.

public class Empregado {

private int matricula;

private String nome;

private long cpf;

private String cargo;

private double salario;

private double gratificacao;

// construtor

public Empregado(int matricula, String nome, long cpf, String cargo, double salario, double

gratificacao) {

this.matricula = matricula;

this.nome = nome;

this.cpf = cpf;

this.cargo = cargo;

this.salario = salario;

this.gratificacao = gratificacao;

}

//... restante do código aqui

}

Os exemplos de classes que foram apresentados até o momento implmentam apenas um construtor. No entanto, se estudarmos a documentação da classe String, por exemplo, veremos que é possível criar cadeias de caracteres chamando o construtor String() sem argumentos, passando uma string literal ou até um array de bytes como argumento. Portanto, concluímos que uma classe pode ter mais de um construtor. Isso é bastante comum e é chamado sobrecarga – conceito que será estudado mais à frente. E mesmo que você tenha vários construtores sobrecarregados, também é aconselhável que a classe tenha um construtor sem argumentos. Isso é importante, principalmente quando é necessário estender a classe. Pois como veremos mais adiante, os construtores da subclasse, implicitamente, fazem uma chamada ao construtor sem argumentos da classe mãe. E se não houver um construtor desses na superclasse, o compilar irá reclamar. Na Listagem 5 é mostrada a classe Empregado com três construtores, incluindo aquele sem argumentos.

Listagem 5. Classe com mais de um construtor.

public class Empregado {

6/12

Page 7: ejm-Orientação a Objetos - uma abordagem com Java - Parte 1

private int matricula;

private String nome;

private long cpf;

private String cargo;

private double salario;

private double gratificacao;

// construtores da classe

public Empregado() {

}

public Empregado(int matricula, String nome) {

this.matricula = matricula;

this.nome = nome;

}

public Empregado(int matricula, String nome, long cpf, String cargo, double salario, double

gratificacao) {

this.matricula = matricula;

this.nome = nome;

this.cpf = cpf;

this.cargo = cargo;

this.salario = salario;

this.gratificacao = gratificacao;

}

//... restante do código aqui

}

A partir dessa definição de classe podemos criar objetos usando cada um desses construtores, da forma que estamos mostrando na Listagem 6. No entanto, o objeto e3 teve todos os seus atributos inicializados, o objeto e2 inicializou apenas dois atributos, e e1 ainda precisa definir valores para todos os seus atributos. Ou seja, dependendo do construtor chamado, pode ser necessário invocar métodos setters para inicializar variáveis de instância.

Listagem 6. Instanciação de objetos.

public class Programa {

public static void main(String[] args) {

Empregado e1 = new Empregado();

Empregado e2 = new Empregado(123, "João da Silva");

Empregado e3 = new Empregado(124, "Antônio José", 12345678901L, "Vendedor", 700.0, 120.0);

}

}

Voltaremos a falar sobre construtores na seção seguinte, quando estudarmos herança.

Modificadores de membros não referentes a acessoExistem vários modificadores que podem ser aplicados a membros de uma classe. No entanto, por

ora, vamos falar apenas de final e static.O modificador final tem funções diferentes quando aplicados a atributos e a métodos. Quando

aplicado a um atributo, indica que este é uma constante. Por exemplo, a classe Math do pacote java.lang – que contém métodos para fazer operações matemáticas básicas – define o campo PI declarado como static. Esse campo define um valor para pi – aproximadamente 3.14 – usado em cálculos matemáticos, tais como a área do círculo. Por padrão, os nomes de campos declarados como final devem ser escritos em maiúsculas.Por sua vez, métodos declarados como final não podem ser sobrescritos nas subclasses.

Estudaremos sobrescrita em uma seção mais adiante. Java utiliza esse modificador quando deseja que o programador utilize forçosamente determinados recursos da API, tais como alguns métodos da classe java.lang.Thread. Essa restrição garante que um método não será substituído, oferecendo certa segurança à sua implementação. Este recurso, entretanto, deve ser usado com cautela, pois invalida muitos dos benefícios da orientação a objetos, tais como a extensibilidade através do polimorfismo, que estudaremos mais à frente.Membros de uma classe podem ser declarados como static quando seu comportamento ou valor

não dependem do estado de um objeto. Suponha que uma classe tem um método que sempre é

7/12

Page 8: ejm-Orientação a Objetos - uma abordagem com Java - Parte 1

executado da mesma maneira, sem depender dos dados da instância, que retorna um número aleatório por exemplo. Ou seja, não é necessário instanciar um objeto para que o método realize a sua função.Em outra situação, imagine que desejamos contar as instâncias de uma classe. Definir esse

contador como uma variável de instância não funcionaria, pois ela seria reinicializada com um valor padrão a cada vez que um novo objeto é criado.Nos dois cenários anteriores, a resposta é usar o modificador static. Membros declarados como

static pertencem à classe, em vez de a uma instância. Um exemplo clássico de static é o método main(), que deve estar presente em toda aplicação. O método main() é o ponto de entrada para um programa Java, e a JVM o executa implicitamente sem que seja necessário instanciar um objeto. Na Listagem 6 mostramos a classe Programa, e como se sabe, esta classe não é instanciada para que main() seja executado. Na Listagem 7 mostramos um exemplo de classe utilizando membros static.

Listagem 7. Código da classe que declara membros estáticos.

public class Matematica {

public static final double PI = 3.14;

public static double perimetroCirculo(double raio) {

return 2 * PI * raio;

}

}

E como acessamos os membros estáticos? Observe como isso é feito na Listagem 8. Note que usamos o operador ponto no nome da classe Matematica, e não na referência a uma instância.

Listagem 8. Acesso aos membros static.

public class Programa {

public static void main(String[] args) {

double raio = 5.0;

System.out.println("Valor de pi: " + Matematica.PI);

System.out.println("Perimetro do círculo: " + Matematica.perimetroCirculo(raio));

}

}

Uma variável ou método estático pertencem à classe e não a uma instância dela, pois eles existem antes mesmo que seja criado qualquer objeto. Por isso, ao criar métodos static, duas restrições importantes devem ser observadas:

⟩ Um método static não pode acessar um membro – método ou atributo – não estático de sua própria classe;

⟩ Métodos static não podem ser sobrescritos. Eles apenas podem ser redefinidos. Na seção onde estudaremos sobrescrita voltaremos a este assunto.

Relacionamentos entre ClassesAo analisar o domínio de um problema percebemos que os objetos normalmente se relacionam.

Por exemplo: um cliente compra produtos em uma loja e é atendido por um vendedor, um aluno cursa uma disciplina que é ministrada por um professor. Quando estudamos os relacionamentos aprendemos que duas classes podem se relacionar através de composição, agregação, associação e generalização/especialização. Composição e agregação são os relacionamentos todo-parte, onde um objeto é parte de outro, como no caso em que Monitor é parte de Computador. Quando um objeto utiliza outro, dizemos que isso é uma associação, tal como Professor ministra Disciplina. A generalização/especialização ocorre quando um objeto herda características e comportamento de outro (herança), como no caso de Cachorro é um Animal. No entanto, esses relacionamentos se resumem aos chamados TEM-UM – nos casos de composição, agregação e composição – e É-UM – para o caso de generalização/especialização. Portanto, o que nos interessa dentro do escopo do nosso artigo são o relacionamento TEM-UM e o relacionamento É-UM.

8/12

Page 9: ejm-Orientação a Objetos - uma abordagem com Java - Parte 1

Relacionamento TEM-UMEste relacionamento é baseado na utilização de uma classe por outra, inclusive nos casos de

composição e agregação. Ou seja, se uma classe A TEM-UM B, então o código de A tem uma referência a uma instância de B. Para exemplificar este relacionamento, considere o caso do relacionamento Pessoa TEM-UM Endereco. Neste exemplo, a classe Pessoa deve ter um campo do tipo Endereco, conforme mostra a Listagem 9. Na Listagem 10 apresentamos o código da classe Endereco.

Listagem 9. Implementação do relacionamento TEM-UM.

public class Pessoa {

private String nome;

// variável de instância que define o relacionamento TEM-UM

private Endereco endereco;

public Pessoa() {

}

public Pessoa(String nome, Endereco endereco) {

this.nome = nome;

this.endereco = endereco;

}

public String getNome() {

return nome;

}

public void setNome(String nome) {

this.nome = nome;

}

public Endereco getEndereco() {

return endereco;

}

public void setEndereco(Endereco endereco) {

this.endereco = endereco;

}

}

Listagem 10. Classe Endereco usada por Pessoa.

public class Endereco {

private String logradouro;

private int numero;

private String complemento;

private int cep;

private String cidade;

private String estado;

public Endereco() {

}

public Endereco(String logradouro, int numero) {

this.logradouro = logradouro;

this.numero = numero;

}

public Endereco(String logradouro, int numero, String complemento, int cep, String cidade, String

estado) {

this.logradouro = logradouro;

this.numero = numero;

this.complemento = complemento;

this.cep = cep;

this.cidade = cidade;

this.estado = estado;

}

//... métodos getters e setters aqui

}

9/12

Page 10: ejm-Orientação a Objetos - uma abordagem com Java - Parte 1

A ideia aqui é que a classe Pessoa usa a referência a Endereco, que tem seus métodos chamados por Pessoa sem a necessidade que o código desses métodos seja duplicado. Esta é uma prática totalmente de acordo com as metas de se obter alta coesão (objeto com finalidade muito bem definida) e baixo acoplamento (grau de interdependência entre as classes). Genericamente falando, se uma classe A TEM-UM B, então A aparenta ter também o comportamento B, pois métodos de B podem ser invocados através de A. No entanto, quando os métodos de B precisam ser chamados, A simplesmente delega a B a execução de tais tarefas.Um exemplo de instanciação de um objeto Pessoa é mostrado na Listagem 11. Note que é

necessário instanciar um Endereco para podermos criar um objeto Pessoa. Observe também que não é necessário usar uma referência explícita a Endereco, podemos simplesmente instanciar o objeto na própria chamada ao construtor de Pessoa. E esse procedimento sempre pode ser usado quando um construtor ou método espera receber um objeto como argumento.

Listagem 11. Exemplo de instanciação de um relacionamento TEM-UM.

public class Main {

public static void main(String[] args) {

Pessoa p = new Pessoa("João da Silva", new Endereco("Rua Maravilha", 55));

}

}

Note que ao utilizarmos a classe Pessoa, desejamos que ela faça realmente as tarefas definidas em seu comportamento, de acordo com o princípio da coesão, mesmo que para isso ela precise solicitar o serviço a outras classes, tais como Endereco. Pois uma classe pode ter a colaboração de outras para cumprir o seu objetivo. Entretanto, Pessoa jamais deve implementar tarefas que são de Endereco, pois uma das preocupações do projeto de software orientado a objetos é com a reutilização. Se, por exemplo, precisarmos definir uma nova classe chamada Empresa, constatamos que esta também TEM-UM Endereco, e se não modelarmos endereço como uma classe, sua implementação também teria que ser parte de Empresa, contrariando o princípio do reuso. Portanto, as classes devem ser projetadas de forma a podermos reutilizá-las em outros contextos.

Relacionamento É-UMO relacionamento generalização/especialização ocorre quando uma classe B é um subtipo de outra,

A. A esse relacionamento chamamos comumente de herança, e é neste, além da implementação de interfaces, que se baseia o conceito É-UM. Mas, por enquanto, falaremos apenas de herança de classes, pois ainda não estudamos interfaces. Generalização/especialização é o relacionamento onde uma classe (subclasse) estende outra (superclasse), aproveitando suas características e comportamento. Por exemplo, sabe-se que professor é uma pessoa. Desta forma, podemos dizer que a classe Professor É-UM(a) Pessoa. Em Java, o relacionamento É-UM entre classes é definido através da palavra-chave extends – ou implements no caso de interfaces. A seguir, mostramos na Listagem 12 o código da classe Professor, a qual estende a classe Pessoa, apresentada na Listagem

9.

Listagem 12. Implementação do relacionamento É-UM.

public class Professor extends Pessoa {

private double horaAula;

public Professor() {

}

public Professor(String nome, Endereco endereco, double horaAula) {

super(nome, endereco);

this.horaAula = horaAula;

}

public double getHoraAula() {

return horaAula;

}

public void setHoraAula(double horaAula) {

this.horaAula = horaAula;

10/1

Page 11: ejm-Orientação a Objetos - uma abordagem com Java - Parte 1

}

}

Observe que Professor incluiu uma característica denominada horaAula, tendo herdado de Pessoa os demais atributos. Na Listagem 13 podemos ver um exemplo de criação de um objeto Professor.

Listagem 13. Criação de um objeto de uma subclasse

public class Main {

public static void main(String[] args) {

Professor p = new Professor("João da Silva", new Endereco("Rua Maravilha", 55), 20.00);

}

}

Um detalhe aqui merece uma atenção especial. Um dos construtores de Professor recebe como parâmetros o nome e o endereço, que são atributos herdados de Pessoa. E estes parâmetros são usados na chamada super(nome, endereco) no código de implementação do construtor. A palavra-chave super é usada para referenciar membros da superclasse. Vamos então falar um pouco mais sobre construtores para entender isso.Antes de retornamos ao assunto construtores, no entanto, é importante sabermos que toda classe

em Java É-UM Object. Object é uma classe da API que é superclasse de todas as classes existentes em Java ou daquelas que são criadas pelo desenvolvedor. Quando, na definição da classe, não for especificada uma superclasse – através do uso de extends – o compilador assume que a superclasse é Object. Do nosso exemplo anterior, concluímos, portanto que Professor estende Pessoa, que por sua vez estende Object.Voltando a falar de construtores, podemos afirmar que todo construtor invoca o construtor de sua

superclasse com uma chamada implícita a super(). A exceção ocorre quando o construtor chama um construtor sobrecarregado da mesma classe, usando this(). Falaremos sobre isso daqui a pouco. Desta forma, quando new Professor() é executado, o construtor de Pessoa será chamado. E quando o construtor de Pessoa é chamado, o de Object também é. Então, vamos relacionar mais algumas regras de construtores, além daquelas que já foram citadas:

⟩ Se você criar um construtor com argumentos e precisar de um sem argumentos, então você terá que criá-lo;

⟩ A primeira instrução de um construtor deve ser uma chamada a super() ou a um construtor sobrecarregado – por meio de uma chamada a this();

⟩ Se você definir um construtor e não inserir uma chamada a this() ou a super(), o compilador irá inserir automaticamente uma chamada a super(), sem argumentos;

⟩ Uma chamada a super() pode incluir argumentos, que são passados ao construtor da superclasse;

⟩ Um construtor não pode ser chamado a partir de um método.

É uma boa prática incluir sempre um construtor sem argumentos em suas classes, mesmo que existam construtores com argumentos. Como já falamos antes, em cada construtor de uma subclasse o compilador insere chamadas ao construtor sem argumentos da superclasse se uma chamada explícita a super() ou a this() for encontrada. Portanto, ao adotar esta prática, evitamos que o compilador reclame quando ele tentar inserir chamadas a super() nos construtores das subclasses.Na classe Professor, apresentada na Listagem 12, há um construtor com argumentos que faz uma

chamada a super(nome, endereco). Essa chamada irá inicializar os atributos da superclasse Pessoa conforme uma das regras acima, e assim este construtor não terá uma chamada a super() sem argumentos.

ConclusõesEscrever aplicações que sejam fáceis de estender e manter é uma missão crucial de todo

desenvolvedor. O paradigma de orientação a objetos oferece uma maneira de criar programas que

11/1

Page 12: ejm-Orientação a Objetos - uma abordagem com Java - Parte 1

facilitam o alcance desse objetivo. Pensando nisso, neste primeiro artigo da série introduzimos alguns conceitos e práticas que podem ajudar muito aos programadores em seus primeiros passos na programação orientada a objetos. O entendimento desses conceitos e de algumas regras aqui estudadas irão facilitar muito a compreensão e aplicação de novos tópicos relacionados, tais como sobrescrita, sobrecarga, classes abstratas, interfaces e polimorfismo, que deixamos para a parte seguinte da matéria.

Linksccuec.unicamp.br/revista/infotec/artigos/leite_rahal.html Programação Orientada ao Objeto: uma abordagem didática

LivrosSun Certified Programmer for Java 6 – Study Guide, Kate Sierra e Bert Bates, McGrawHill, 2008 Excelente livro para aqueles que desejam se preparar para o SCJP

De que se trata o artigo:Apresenta os conceitos iniciais de orientação a objetos, de forma leve, tentando mostrar exemplos

práticos em Java que tornem mais fácil o entendimento do paradigma. Também antecipa brevemente alguns temas que serão abordados no futuro.

Para que serve:Serve como um guia básico aos desenvolvedores que estão dando os primeiros passos na

programação orientada a objetos e também àqueles que estão se preparando para o SCJP – Certificação para Programador Java.

Em que situação o tema é útil:A programação em Java baseia-se em classes, e compreender os conceitos de orientação a objetos

é fundamental para escrever programas cujos módulos podem ser reutilizáveis e escaláveis. Os sistemas estão cada vez mais complexos, e criar programas que sejam fáceis de manter depende do entendimento dos recursos do paradigma OO e da linguagem de programação utilizada.

Resumo DevMan:Neste artigo, serão apresentados os conceitos iniciais da orientação a objetos e como eles podem

ser implementados em Java. Para isso, mostramos como organizar classes em pacotes e como criar classes preocupando-se principalmente com o encapsulamento e a coesão. O texto fala sobre os relacionamentos entre as classes, e apresentamos exemplos de como implementar esses relacionamentos, principalmente TEM-UM (que reune composição, agregação e associação) e É-UM (que corresponde à generalização/especialização). Os contrutores e seu encadeamento são estudados com detalhes, principalmente as regras que devem ser observadas na sua criação.

Carlos Araújo ([email protected]) é professor do curso de Sistemas de Informação no Centro Universitário Luterano de Santarém – Pará. Leciona Estruturas de Dados e Linguagem de Programação Orientada a Objetos usando Java, desenvolve sistemas há 20 anos e é certificado SCJP. Mantém o blog http://professorcarlos.blogspot.com.

12/1