1. UNIVERSIDADE FEDERAL RURAL DE PERNAMBUCO (UFRPE)
COORDENAÇÃO GERAL DE EDUCAÇÃO A DISTÂNCIA (EAD/UFRPE)
Programação II
Fernando Antonio Mota Trinta
Volume 1
Recife, 2010
2. Universidade Federal Rural de Pernambuco
Reitor: Prof. Valmar Corrêa de Andrade
Vice-Reitor: Prof. Reginaldo Barros
Pró-Reitor de Administração: Prof. Francisco Fernando Ramos Carvalho
Pró-Reitor de Extensão: Prof. Paulo Donizeti Siepierski
Pró-Reitor de Pesquisa e Pós-Graduação: Prof. Fernando José Freire
Pró-Reitor de Planejamento: Prof. Rinaldo Luiz Caraciolo Ferreira
Pró-Reitora de Ensino de Graduação: Profª. Maria José de Sena
Coordenação Geral de Ensino a Distância: Profª Marizete Silva Santos
Produção Gráfica e Editorial
Capa e Editoração: Allyson Vila Nova, Rafael Lira, Italo Amorim e Gláucia Fagundes
Revisão Ortográfica: Marcelo Melo
Ilustrações: Allyson Vila Nova
Coordenação de Produção: Marizete Silva Santos
3. Sumário
Apresentação................................................................................................................. 4
Conhecendo o Volume 1 ................................................................................................ 5
Capítulo 1 – Introdução a Orientação a Objetos ............................................................. 7
1. Introdução ...................................................................................................................7
2. Evolução das Linguagens de Programação ..................................................................8
3. Paradigmas de Programação .....................................................................................10
Capítulo 2 – Orientação a Objetos: conceitos principais ............................................... 22
1. Introdução .................................................................................................................22
2. Abstração ...................................................................................................................23
Capítulo 3 – A Linguagem Java ..................................................................................... 37
1. Introdução .................................................................................................................37
2. Características da Linguagem ....................................................................................38
3. Máquina Virtual Java: o alcance de portabilidade .....................................................39
4. Codificando um primeiro programa Java ...................................................................40
5. JSDK – Java Software Development Kit ......................................................................42
6. JCreator: Ambiente de programação de iniciantes ....................................................44
Conheça o Autor .......................................................................................................... 50
4. Apresentação
Caro(a) Cursista,
Seja bem-vindo(a) ao curso de Programação II. Este curso é composto por 4 volumes. Neste primeiro
volume, vamos estudar a teoria e os principais conceitos relacionados ao paradigma de programação baseado
em objetos. Além de apresentar suas vantagens em relação a outros paradigmas de programação, será também
apresentada a linguagem de programação Java, a ser utilizada no curso para atividades práticas, como exemplos e
exercícios.
O segundo volume é dedicado em sua totalidade à sintaxe Java, seu sistema de tipos, operadores lógicos,
comandos de decisão e repetição. No terceiro volume você aprenderá a utilizar os conceitos de orientação a objetos
utilizando a sintaxe apresentada no volume anterior.
Por fim, no quarto e último volume serão abordados assuntos ainda mais avançados, como a ideia de
herança, polimorfismo e tratamento de exceções.
Bons estudos!
Fernando Trinta
Professor Autor
4
5. Programação II
Conhecendo o Volume 1
Neste primeiro volume, você irá encontrar o Módulo 1 da disciplina Programação II.
Para facilitar seus estudos, veja a organização deste primeiro módulo.
Módulo 1 - Uma Introdução ao Paradigma Orientado a Objetos
Carga Horária do Módulo 1: 15 h
Objetivo do Módulo 1: Introduzir o paradigma de programação orientada a
objetos, a partir de um histórico evolutivo dos paradigmas de programação, ressaltando
seus benefícios em relação aos demais. Apresentar os principais conceitos do paradigma
orientado a objetos. Introduzir a linguagem de programação orientada a objetos Java, que
será utilizada no curso para utilização prática da teoria explanada.
Conteúdo Programático do Módulo 1
» Introdução (Evolução dos paradigmas computacionais. O problema da
complexidade. A ideia da abstração. Uma nova forma de encarar o problema:
Objetos. Vantagens.)
» Conceitos Fundamentais do Paradigma OO (Objetos. Classes. Métodos. Atributos.
Mensagens. Polimorfismo. Herança.)
» A linguagem de Programação Java (Histórico. Estrutura do código em Java.
Compilação. Execução. O método main(). Escrevendo na tela. Independência de
Plataforma/Máquina Virtual. Ambientes de programação.
5
6. Programação II
Capítulo 1
O que vamos estudar neste capítulo?
Neste capítulo, vamos estudar os seguintes temas:
» Introdução ao Paradigma Orientado a Objetos.
» Evolução dos paradigmas computacionais.
» O problema da complexidade.
» A ideia da abstração.
» Uma nova forma de encarar o problema: Objetos.
» Vantagens e Desvantagens do Paradigma Orientado a Objetos.
Metas
Após o estudo deste capítulo, esperamos que você consiga:
» Identificar as principais características do paradigma de programação orientado a
objetos.
» Descrever as vantagens do paradigma orientado a objetos em relação aos demais
paradigmas.
6
7. Programação II
Capítulo 1 – Introdução a Orientação a
Objetos
Vamos conversar sobre o assunto?
Caro(a) Cursista, é indiscutível o papel que a tecnologia da informação tem hoje
na sociedade moderna. Praticamente toda a economia mundial está fortemente ligada a
sistemas computacionais que gerenciam bancos, sistemas comerciais, órgãos públicos,
dentre outros. Você alguma vez parou para pensar em como estes sistemas foram e
continuam sendo construídos?! Não?! Então é hora de você começar a refletir sobre como
se dá o processo de construção dos programas que realizam boa parte dos complexos
sistemas computacionais modernos. E para isto, nós vamos abordar neste volume um pouco
sobre o modelo de programação mais difundido atualmente, o paradigma baseado em
objetos. Vamos lá?
1. Introdução
Nas últimas décadas, a sociedade moderna vem cada vez mais sendo influenciada
por sistemas de informação baseados em computador. Desde sistemas de comércio
eletrônico, passando por jogos de computador e chegando até complexos sistemas militares,
o mundo moderno é cada vez mais refém da tecnologia e dos sistemas de informação. Você
consegue se imaginar em um mundo hoje sem computadores? Sem Internet? É difícil de
imaginar. Este parece ser um caminho sem volta que fica ainda mais visível ao passo que nos
últimos anos têm crescido o uso de aplicações em dispositivos móveis, como celulares, uso
de novas aplicações baseadas em tecnologias inovadoras, como GPS, dentre outros.
É inegável que as possibilidades do uso de novas tecnologias são importantes
facilitadores para a vida moderna, como no caso de você querer utilizar o seu celular para,
baseado na sua atual localização, descobrir qual a farmácia mais próxima ou o melhor
caminho para chegar a determinado lugar. Porém, do ponto de vista de quem constrói os
novos sistemas, isso tem se tornado um pesadelo, uma vez aumenta a complexidade destas
aplicações.
Construir as aplicações atuais requer uma série de passos que ajudem a tratar
a complexidade destes sistemas. Estes passos e as pessoas que são envolvidas no
desenvolvimento de sistemas criaram uma disciplina, conhecida hoje como Engenharia
de Software. Esta disciplina ou processo de desenvolvimento concentra as atividades de
entender o que um sistema deve fazer (análise), propor uma solução computacional
(projeto) e desenvolver esta solução (implementar/codificar) (Figura 1). Esta última fase
está diretamente relacionada ao tema desta disciplina: criar aplicações a partir de uma
linguagem de programação.
Figura 1 – Processo Resumido de Desenvolvimento de Aplicações
7
8. Programação II
A ideia de codificar representa uma forma de escrever por meio de uma linguagem
de programação, o projeto da solução proposta nas fases anteriores da engenharia de
software. Informalmente, uma linguagem de programação pode ser definida como um
conjunto limitado de instruções (vocabulário), associado a um conjunto de regras (sintaxe)
que define como as instruções podem ser associadas. Ou seja, como se pode compor os
programas para a resolução de um determinado problema.
Os programas escritos em uma linguagem de programação são então traduzidos
para uma linguagem de máquina, que é compreendida pelo computador. O resultado desta
conversão são programas executáveis que realizam uma série de ações, como solicitar
dados, realizar cálculos específicos ou apresentar dados para usuários.
Ao longo dos anos, foram desenvolvidas (e continuam sendo) uma grande
quantidade de linguagens de programação, algumas de uso mais geral e outras concebidas
para áreas de aplicação específicas. Porém, apesar de suas diferenças, estas linguagens são
geralmente agrupadas de acordo com as características principais que indicam como um
programador (quem escreve o programa) enxerga a forma de organizar os programas. As
diferentes maneiras como uma linguagem organiza seus conceitos é chamada de paradigma
de programação. Dentre alguns dos mais conhecidos paradigmas de programação podem-
se citar: paradigma imperativo, paradigma estruturado e o paradigma orientado a objetos.
A tabela a seguir indica o índice de popularidade dos principais paradigmas de programação
existentes:
Tabela 1 – Distribuição de Paradigmas segundo índice de popularidade
[Fonte: http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html]
Paradigma Indice de Popularidade
Orientado a Objetos 54,6%
Procedural 41,5%
Funcional 2,8%
Lógico 1,1%
Neste momento, caro(a) cursista, você pode se perguntar: para que diferentes
paradigmas? Para responder esta questão é necessário voltar no tempo e entender como as
linguagens de programação evoluíram ao longo dos anos.
2. Evolução das Linguagens de Programação
2.1. A linguagem de máquina e a linguagem montadora
Um computador é organizado em um conjunto de circuitos eletrônicos. Desta
forma, seu controle é feito ainda através de uma forma bem primitiva, baseado em um
sistema binário para representação de dados e operações. Esta representação é chamada
de código binário ou linguagem de máquina, por ser compreendida e executada no
hardware do computador. Esta linguagem consiste de uma grande sequência de números
(1s e 0s), que instruem os computadores a realizar suas operações mais elementares, uma
de cada vez. A figura a seguir apresenta um trecho de um possível programa em linguagem
de máquina.
8
9. Programação II
Figura 2 – Código de Máquina
Criar programas em código binário é impraticável. Para facilitar a construção
dos programas, uma nova linguagem de programação foi proposta, onde eram utilizadas
abreviações para representar as operações elementares. As linguagens que seguiram esta
estratégia foram chamadas linguagens montadoras ou assembly. Em Assembly, em vez
de números binários, utiliza-se palavras abreviadas, chamadas também de mnemônicos,
indicando a operação a ser realizada. Estas linguagens estavam mais próximas do
entendimento dos humanos, e através de um programa chamado montador, o código
binário era gerado e podia a ser executado nos computadores da época. Abaixo são
apresentados dois exemplos de instruções Assembly:
» MOV R1, R2 – A instrução MOV (abreviatura do termo em inglês MOVE) recebe
como parâmetros, dois registradores: R1 e R2. Sua execução determina a cópia do
conteúdo de R2 para R1 (equivalente R1:=R2, sendo R1 e R2 equivalente a duas
variáveis);
» ADD R1, R2 – A instrução ADD (abreviatura de ADDITION) recebe como parâmetro,
dois registradores como parâmetros: R1 e R2. Sua execução adiciona o conteúdo
de R1 ao de R2 e o resultado é armazenado em R1 (equivalente à instrução Pascal
R1:=R1+R2).
Utilizando estes comandos, os programadores utilizaram Assembly para realizar
seus programas. Por exemplo, para somar dois inteiros, um programa em Assembly poderia
ser escrito através da rotina descrita abaixo.
MOV AX,B ; registro AX recebe o valor de memória contida na variável B
ADD AX,C ; AX recebe a soma de AX (valor de B) com o valor de C
MOV A,AX ; variável A recebe valor de AX
Dá para verificar que o mesmo ainda está bem próximo da linguagem da máquina
que do homem. Difícil de entender!? Bastante! Porém, isso na época permitiu que se
facilitasse a forma de se construir os programas de computador.
2.2. Aumentando o nível de abstração das linguagens
O uso dos computadores foi se popularizando. Com isso, novas aplicações foram
propostas para os computadores que surgiram. Com isso, tornou-se necessário facilitar
ainda mais a forma como os programas de computadores eram escritos. Surgiram então as
chamadas linguagens de programação de alto nível, onde uma única instrução poderia ser
utilizada para realizar várias operações elementares do computador. Um programa tradutor
chamado compilador transformava o código em alto nível em linguagem de máquina.
Programas passaram a ser escritos em uma linguagem mais próxima da linguagem natural e
utilizavam notações matemáticas semelhantes ao cotidiano das pessoas. Um programa para
calcular o fatorial de um número poderia ser escrito como na figura a seguir.
9
10. Programação II
int fatorial(int n)
{
if (n == 0)
return 1;
else{
fat = 1;
for (int i=2:i<=n){
fat = fat * i;
}
return fat;
}
}
Figura 3 – Trecho de um programa para cálculo de um fatorial escrito na linguagem C
Se mesmo para você esta versão em uma linguagem de alto nível ainda seja
incompreensível, pelo menos se percebe que são utilizados menos construções para se
realizar a mesma tarefa que em uma linguagem montadora. Desta forma, os programas
ficaram mais fáceis de serem compreendidos por nós, humanos. Isto facilitou bastante a
programação e, principalmente, a manutenção e correção de erros nos programas. Várias
foram as linguagens de alto nível propostas. Dentre algumas, pode-se citar Fortran, Pascal,
Basic e C. Algumas delas são bem populares ainda hoje.
O surgimento das linguagens de alto nível auxiliou programadores a construir
programas mais enxutos, mas fáceis de compreender e também de corrigir erros... Mas
onde está o conceito de paradigma nesta história?! Vamos lá então...
3. Paradigmas de Programação
3.1. Paradigma Imperativo
Como vimos anteriormente, um paradigma de programação explica como
elementos que compõem um programa são organizados e interagem entre si. No caso
das linguagens montadoras ou das linguagens de alto nível, a construção dos programas é
realizada através de instruções que indicam o que o computador deve realizar. A diferença
é que para linguagens montadoras, o programador precisava ser muito mais detalhista que
em uma linguagem de alto nível. Um simples comando na linguagem C era equivalente
a dezenas de linhas em uma linguagem de baixo nível. Porém, a ideia era a mesma: um
conjunto de instruções que ilustram uma estratégia de um ser humano em “COMO” resolver
um problema.
Esta forma de organizar os programas estabelece um primeiro paradigma de
programação conhecido: o paradigma imperativo. Programar um computador neste
paradigma significa “dar-lhe ordens” que são executadas sequencialmente. O paradigma
imperativo é profundamente influenciado pela arquitetura como os próprios computadores
são organizados, a arquitetura de von Neumann. Neste modelo, você deve pensar em um
programa como um computador que tem memória que guarda um estado por meio de
10
11. Programação II
dados (variáveis), e que recebe instruções que modificam e apresentam estes dados, como
ilustrado na figura a seguir:
Figura 4 – Modelo Imperativo de programação
Como todas as arquiteturas de computadores existentes hoje se baseiam na
arquitetura de Von Neumann, os ambientes de execução das linguagens imperativas são
bastante eficientes.
Porém, mesmo assim, programas escritos no paradigma imperativo ainda são
muito detalhistas, o que requer um grande esforço na construção e, principalmente,
na manutenção dos programas. Conforme a complexidade dos programas aumenta, a
legibilidade, ou seja, a facilidade com que os programas podem ser lidos e entendidos,
torna-se também mais difícil. Com isso, a manutenção continuou sendo um problema, e
erros acabavam sendo introduzidos sem querer durante o processo de depuração (correção)
de programas.
3.2. Paradigma Procedural
Uma evolução do paradigma imperativo foi a percepção que certas sequencias de
instruções que são repetidas em vários pontos de um mesmo programa. Estes sequências de
passos poderiam ser agrupadas em procedimentos e funções (também chamados de sub-
rotinas) definidas pelo programador e serem chamadas de diversos pontos do programa,
como no caso de criarmos uma função que calcule o valor do fatorial de número (Figura 5).
Por esta característica, o paradigma imperativo também é conhecido na literatura como o
paradigma procedural.
Figura 5 – Paradigma Imperativo VS Paradigma Procedural
O paradigma procedural foi durante muitos anos o padrão para linguagens de
programação. Este paradigma acabou influenciando também a forma como a própria análise
e projeto das aplicações eram realizados. O paradigma procedural induzia que sistemas
mais complexos precisavam ser “quebrados” em partes menores, mas fáceis de lidar. Este
princípio de “dividir para conquistar” é a abordagem padrão que os desenvolvedores de
sistemas utilizam para lidar com sistemas complexos.
No caso do paradigma procedural, o sistema era dividido em muitas funções
que representavam as diversas funcionalidades que o programa tinha que fazer. Isso era
11
12. Programação II
repetido até se chegar a um nível que as funções eram claras o suficiente para entender
seu funcionamento, ou mesmo para serem reaproveitadas em diferentes pontos de um
programa. Com procedimentos, funções menores eram mais fáceis de entender, além de
facilitar eventuais correções de um programa, em caso de erros.
De certa forma, esta abordagem de lidar com a complexidade também lida com um
conceito chave para a construção de sistemas e a programação de aplicações: a abstração.
A ideia de abstração faz com que os desenvolvedores concentrem seus esforços no que é
relevante, deixando detalhes irrelevantes para serem tratados mais tarde. Abstração dentro
de um paradigma de programação indica como os problemas são encarados. Os conceitos
utilizados para resolver estes problemas são as abstrações utilizadas por um paradigma.
No caso do paradigma procedural, as abstrações utilizadas para decompor um problema
complexo são as sub-rotinas (funções e/ou procedimentos).
O uso de sub-rotinas separa dados (variáveis) e operações (procedimentos) sobre
os dados, onde a ideia de funções e procedimentos manipulando um conjunto de dados
surgiu como um modelo natural para representar como as aplicações.
Porém, esta abordagem também apresenta seus problemas. Primeiro, o caminho de
execução do programa ficou mais complexo devido à chamada de funções e procedimentos
em diversas partes do código. A confusão de chamadas e desvios era tão grande que se
criou um termo específico para os programas mal-estruturados a partir de várias chamadas
de sub-rotinas, o “código espaguete” (Figura 6).
Figura 6 – O código espaguete
[Fonte: http://www.yourdictionary.com/images/computer/SPAGETI.GIF]
No entanto, o maior problema está relacionado com a separação de dados e
procedimentos, pois várias partes de um mesmo programa podiam alterar os valores de
variáveis compartilhadas. Em muitos casos, a modificação de uma variável por uma sub-rotina
causava a falha na execução de outra sub-rotina que dependia do valor da mesma variável.
12
13. Programação II
Além disso, como procedimentos utilizavam valores em variáveis separadas, uma alteração
na representação de uma variável poderia exigir uma alteração em cascata em todas as sub-
rotinas que dependessem deste valor. Ou seja, um pesadelo para o programador!
Figura 7 – Relação entre dados e procedimentos no paradigma procedural
Vejamos isso em exemplo mais claro, como o cálculo de uma folha de pagamento
de uma empresa. Neste caso, os dados relativos a cada um dos funcionários ficariam
espalhados no programa. Seria necessário recuperar estes dados para cada funcionário
obter e então realizar o processamento do cálculo da folha de pagamento (Figura 8). Este
processamento seria representado por uma função ou uma sub-rotina.
Figura 8 – Cálculo de uma folha de pagamento no paradigma procedural
Com o aumento da complexidade das aplicações, o gerenciamento entre
procedimentos e dados dispersos criava um pesadelo para os programadores que precisavam
realizar manutenções no sistema. Modificações em um dado poderiam desencadear um
processo de atualização em vários procedimentos.
Figura 9 – Dificuldade do gerenciamento de dados e procedimentos no paradigma procedural
Além disso, verificou-se que ao longo dos anos, os procedimentos mudavam muito
mais que os dados manipulados. Como exemplo, as regras que podem ser associadas ao
cálculo do salário de um funcionário variam muito mais que os dados do funcionário,
como seu nome ou seu cargo. Desta forma, era necessária uma nova forma de organizar os
sistemas.
13
14. Programação II
3.3 Programação Modular e Estruturada
Para tentar melhorar as deficiências do paradigma procedural, surgiu
então a programação modular. Nesta abordagem, o programa é dividido em vários
componentes ou módulos. Ao contrário do paradigma procedural, módulos combinam
dados e procedimentos. Um módulo é composto de dados que são manipulados pelos
procedimentos deste módulo. Cada módulo fornece um conjunto de procedimentos para
que outras partes de um programa possam acessar dados internos de um módulo. Este
conjunto de procedimentos ou funções é conhecido como interface do módulo. Desta
forma, dados internos de um módulo são protegidos contra manipulações indevidas de
outros módulos, em um princípio conhecido como encapsulamento.
Estas características trouxeram vantagens em relação aos seus predecessores.
Dentre algumas, pode-se citar:
» Estrutura do programa fica mais clara ao agrupar funções e variáveis relacionadas
em um mesmo módulo;
» Possibilidade de alterar módulos separadamente. Uma vez que cada módulo
preserve sua interface visualizada por outros módulos, modificações internas e
novos procedimentos podem ser incluídos;
» O acesso a dados internos de um módulo é feito através de sua interface. Com
isso, tem-se um maior controle sobre operações indevidas sobre estes dados. Por
exemplo, é possível verificar se um dado pode ou não ser modificado para um
determinado valor, antes de se efetivar a mudança.
Este estilo de programação também muito referenciada como programação
estruturada, devido a uma melhor organização dos conceitos utilizados nos programas.
A programação estruturada tornou-se um sucesso e, a partir de seus conceitos, métodos
de modelagem e análise foram propostos para facilitar o projeto das novas aplicações que
surgiam na época em que surgiu a programação estruturada, como a Análise Estruturada de
Tom DeMarco, e a Análise Estruturada Moderna, de Edward Yourdon.
Com as ideias de módulos, encapsulamento e interfaces, a programação modular
permitiu um melhor aproveitamento e distribuição de programas. Os módulos construídos
por um programador poderiam ser reaproveitados por outros programadores, sem a
necessidade de se entregar código-fonte. Bastava que os programadores que desejassem
utilizar um módulo soubesse a interface deste módulo. Imagine um módulo como uma
biblioteca de funções. Poderia haver um módulo para fazer, por exemplo, a verificação se
um CPF é válido ou não. A única coisa que um programador precisaria saber é que este
módulo possui uma função chamada valida_cpf, que recebe como parâmetro um número
de 11 dígitos e retorna verdadeiro se este número representa um CPF válido. Para os
programadores que vão utilizar o módulo, ele é considerado uma caixa-preta, no sentido
que não se sabe como ele funciona internamente. O importante é que ele forneça o
resultado que se espera dele.
Porém, a programação modular também apresenta seus problemas. O primeiro
e principal problema é o fato que módulos não eram naturalmente extensíveis. Isso
significava que para fazer alterações em módulo, era necessário entrar no código-fonte e
diretamente realizar as modificações. E em muitas situações, se via a necessidade de criar
novas funcionalidades dos programas a partir de módulos já existentes.
14
15. Programação II
3.4. O Paradigma Orientado a Objetos
O termo “programação orientada a objetos” foi criado
por Alan Kay (imagem ao lado), autor da linguagem Smalltalk.
Sua proposta visa aproximar a maneira como construímos os
programas de computador do mundo real.
O mundo real é composto por entidades que interagem,
que trocam serviços entre si para realizar suas tarefas. Na
proposta de orientação a objetos, estas entidades são chamadas
objetos.
Objetos podem representar tanto entidades concretas (como uma pessoa ou
um livro) quanto entidades abstratas (como uma transação bancária ou uma sessão de
um usuário ao acessar um site). Para identificar objetos são empregadas técnicas que os
próprios seres humanos utilizam para organizar seus pensamentos, como classificação de
elementos, diferenciação entre parte e todo, troca de mensagens, dentre outros.
Alan Kay utilizou uma analogia com um sistema molecular, batizada então de
“analogia biológica”. Nesta proposta, ele se questionou: “como seria um sistema de
computador que se organizasse como um ser vivo?”. Sua proposta estabelecia que:
» O sistema seria organizado em células, onde cada célula se comportaria como uma
unidade autônoma;
» Cada “célula” interagiria com outras células através do envio de mensagens para
realizar um objetivo comum;
Para Kay, objetos do mundo real também se comportam como estas células. A
partir de sua analogia biológica, ele estabeleceu os princípios básicos para a orientação a
objetos. São estes:
1. Qualquer coisa é um objeto;
2. Objetos realizam tarefas através da requisição de serviços a outros objetos;
3. Cada objeto pertence a uma determinada classe. Uma classe agrupa objetos
similares;
4. A classe é um repositório para comportamento associado ao objeto;
5. Classes são organizadas em hierarquias.
3.4.1. Estabelecendo um exemplo
Para melhorar sua compreensão, vamos utilizar um exemplo prático. Imagine
a situação em que um cliente chamado Ian deseja comprar uma pizza via um serviço de
entrega em domicílio. Ian interage com uma atendente (Maria) e faz sua solicitação.
Maria por sua vez interage com Mário, o pizzaiolo, repassando a solicitação de Ian, uma
vez que quem sabe fazer a pizza é Mário, afinal ele é o pizzaiolo. Após a pizza ficar pronta,
ela é entregue ao Zé, o motoboy, que tem por função entregar a pizza a Ian e receber o
pagamento. A Figura 10 ilustra a interação entre estes personagens.
15
16. Programação II
Figura 10 – Um exemplo para a orientação a objetos na vida real
Embora o exemplo acima ainda possa ser refinado e inserir ainda mais participantes,
ele é suficiente para ilustrar os conceitos propostos pelo paradigma orientado a objetos.
Primeiramente, cada elemento que participa do processo é considerado um objeto. Cada
objeto é uma entidade única no sistema. No caso temos Ian, Maria, Mário e Zé como
objetos. Não existem duas “Marias”. Poderiam até existir duas funcionárias com o mesmo
nome, mas não seriam a mesma pessoa. Cada objeto tem dados ou propriedades que os
identificam. No caso, cada pessoa no processo tem um nome.
Cada objeto interage com os demais por meio da solicitação de serviços. No caso,
Ian solicita seu pedido através da atendente Maria, que lhe fornece os produtos que ela
pode entregar a Ian. De forma semelhante, Maria solicita a Mário a pizza escolhida por Ian,
passando informações necessárias para realização deste serviço, como o sabor da pizza e o
tipo da borda, como ilustrado na Figura 11.
Figura 11 – Solicitação de um serviço através da troca de mensagem entre objetos
Seguindo sua análise, cada objeto pertence a uma determinada classe que agrupa
objetos similares. No nosso exemplo, a pizzaria não atende apenas ao cliente Ian. Podem
existir outras dezenas de clientes que também podem fazer seus pedidos à Pizzaria. Estes
clientes possuem características comuns, como um cadastro na loja ou um endereço de
16
17. Programação II
entrega. Obviamente, para cada cliente, os valores associados a estas propriedades são, em
geral, diferentes. Estes objetos são tratados e agrupados como um tipo único de objeto,
uma classe. Neste caso, poderíamos agrupar Ian, Cris e Evandro como objetos da classe
Cliente, como ilustrado na Figura 12.
Figura 12 – Objetos de uma mesma classe
Por fim, classes são organizadas em hierarquias. No nosso exemplo, Maria, Mário
e Zé poderiam ser agrupados em uma classe que representem os funcionários da Pizzaria
(Figura 13). Estes possuem características comuns como um salário, um horário de trabalho.
Porém, estes também poderiam ser classificados como pessoas, com atributos como nome,
sexo, dentre outros. Neste caso, a classe Pessoa englobaria tanto funcionários quanto
clientes, indicando que existem certas propriedades comuns, como o fato de tanto clientes
quanto funcionários possuírem um nome.
Figura 13 – Hierarquias de classes de objetos
Mas vistos estes conceitos, em que a orientação a objetos pode melhorar a forma
como programamos nossas aplicações? Para isso, vamos pegar o exemplo do cálculo de
uma folha de pagamento de uma empresa, como ilustrado na Figura 14.
Figura 14 – Uma folha de pagamento no modelo estruturado
17
18. Programação II
Relembrando: como os dados e funções são separados, calcular o custo total da
folha requer que saibamos individualmente todos os dados de cada funcionário, o que
acabava por fazer com que a evolução do sistema ficasse bastante complicada. Agora veja
como seria uma possível versão utilizando a abordagem orientada a objetos (Figura 15).
Figura 15 – Uma folha de pagamento no modelo de objetos
Nesta versão, cada funcionário é representado por um objeto. Um objeto atua
como um módulo que engloba dados, e também fornece operações que manipulam tais
dados. Os dados internos de cada objeto são chamados atributos, enquanto as operações
que um objeto fornece são chamados de métodos. No caso, todo objeto que representa
um funcionário possui como atributos seu nome, seu cargo, seu salário fixo e uma série
de outros atributos que são comuns a qualquer funcionário da empresa. Para se calcular
seu salário, cada funcionário oferece um método chamado calculaSalario, que internamente
utiliza os dados de cada objeto e fornece o valor de seu salário individual. Para calcular o
custo total da folha de pagamento seria então somar os valores obtidos pela chamada de
cada método calculaSalario, em cada objeto que representa um funcionário da empresa.
A orientação a objetos representa uma nova forma de enxergar e modelar o
mundo como um conjunto de objetos inter-relacionados, interagindo por meio da troca de
mensagens. A abstração escolhida (objeto) combina estrutura de dados e comportamento
funcional, fazendo com que os dados sejam preservados e procedimentos modificados
mais facilmente, sem causar impactos tão profundos quanto o paradigma estruturado. A
ocorrência de erros ou alterações vão estar associadas a um único módulo ou a um pequeno
grupo deles, onde é mais fácil depurar e isolar erros.
Esta forma de organizar os programas cria um modelo mais intuitivo e fácil
de usar, mas principalmente, aumenta a possibilidade de reuso entre objetos. Objetos
podem ser criados a partir de outros objetos. Por exemplo, você poderia criar um objeto
que representasse um funcionário diferenciado, como aquele cujo salário fosse baseado
em comissões, e não em um salário fixo. Este objeto poderia ser criado através de um
mecanismo especial de extensão chamado Herança. Resumidamente, este novo objeto
teria as mesmas características de um funcionário comum, mas poderia ter dados a mais,
assim como seus métodos poderiam ser modificados, como no caso de seu salário.
3.5. Programação Orientada a Objetos VS Programação
Estruturada
A forma de organizar um programa usando orientação a objetos é essencialmente
diferente do desenvolvimento tradicional de software. Mesmo assim, alguns conceitos do
modelo orientado a objetos podem ser equiparados a conceitos do modelo estruturado,
18
19. Programação II
como visto na figura a seguir.
Figura 16 – Paradigma orientado a objeto VS Paradigma Estruturado
A programação orientada a objetos é especialmente útil à medida que os sistemas
tornam-se cada vez mais complexos, e por consequência, seus programas ficam cada
vez maiores. Grandes programas se beneficiam mais com a modularidade oferecida por
objetos. Dividindo o programa em vários módulos independentes (objetos), aumenta-se a
flexibilidade e a facilidade para manutenção do programa como um todo.
Na programação estruturada, a reusabilidade é limitada a trechos de algoritmos
representados por meio de sub-rotinas. Na programação orientada a objeto é possível
reutilizar todo um módulo, no caso o objeto, com seus métodos e seus atributos. Em geral,
a programação orientada a objetos ainda apresenta como vantagens: código mais lógico,
e melhor encapsulado, uma maior facilidade de manutenção e extensão do código, um
melhor reaproveitamento de código, dentre outros.
Como desvantagens, pode-se afirmar que o aprendizado de uma linguagem
orientada a objetos é mais complexo, principalmente para aqueles já familiarizados com o
paradigma estruturado. É necessário mudar a forma de pensar na solução de um programa,
utilizando conceitos não tão simples quanto do paradigma estruturado. Por fim, dificilmente
uma linguagem orientada a objetos conseguirá ter um desempenho em tempo de execução
superior a linguagens não orientadas a objetos.
Exercícios
1. Com suas palavras conceitue: (a) linguagem de programação e (b) paradigma de
programação.
2. Utilizando a Internet, verifique quais são os outros paradigmas de programação
existentes além dos apresentados neste capítulo. Explique qual o propósito de pelo
menos dois outros paradigmas.
3. Em sua opinião, ainda faz sentido hoje se programar em uma linguagem de baixo
nível como Assembly?! Justifique sua resposta.
4. Para muitos pesquisadores, o paradigma orientado a objetos é apenas uma
extensão do paradigma estruturado. Você concorda ou discorda desta opinião?
Justifique.
5. Utilizando a abordagem de uma programação estruturada, resolva o seguinte
problema. É necessário descobrir o atleta que mais marcou gols em um campeonato
de futebol. Descreva (em português ou em linguagem de programação que você
conheça) como seria solução deste problema.
6. Utilizando o mesmo problema da questão anterior, como seria a solução utilizando
19
20. Programação II
objetos.
Minibiografia
Edsger Wybe Dijkstra (11 de Maio de 1930 — 6 de Agosto de 2002) foi um pesquisador holandês
mundialmente conhecido por suas contribuições nas áreas de desenvolvimento de algoritmos e
programas de linguagens de programação. Foi o precursor da ideia contrária ao uso de comandos
“GOTO” em linguagens de programação, um recurso muito utilizado na sua época. Em seu artigo
“A Case against the GO TO Statement”, Dijkstra alertou para vários erros decorrentes do uso da
declaração GOTO. Uma transcrição deste artigo pode ser visto na URL: http://www.cs.utexas.
edu/~EWD/transcriptions/EWD02xx/EWD215.html. Suas pesquisas impulsionaram o surgimento
da programação estruturada.Em 1972, recebeu o Prêmio Turing por suas contribuições com o
ALGOL e no campo de linguagens de programação.
Vamos Revisar?
Você estudou, neste capítulo, como as linguagens de programação evoluíram ao
longo dos anos no intuito de facilitar a vida dos programadores das aplicações modernas.
Esta evolução foi motivada principalmente pela crescente complexidade dos programas.
Com isso, as linguagens têm focado principalmente em criar abstrações que melhorem a
compreensão e manutenção destes programas. Você também viu que é possível classificar
as linguagens em grupos que seguem o mesmo conjunto de ideias sobre a estruturação e
execução de um programa, os chamados paradigmas de programação. Desde o paradigma
imperativo até o paradigma estruturado, vários foram os avanços na estruturação dos
programas, como a reutilização através de sub-rotinas até o uso de módulos autocontidos
que protegem seus dados de acesso indevido. Apesar destas evoluções, havia muito o que
evoluir, principalmente no que diz respeito ao reuso dos programas. Para isso, uma nova
abordagem de programar foi proposta: a orientação a objetos. Neste novo paradigma,
programas se aproximam mais do mundo, através de objetos que interagem para a
realização de uma tarefa. No próximo capítulo, você verá com mais detalhes os principais
conceitos deste novo paradigma.
20
21. Programação II
Capítulo 2
O que vamos estudar neste capítulo?
Neste capítulo, vamos estudar os seguintes temas:
» Conceitos fundamentais do paradigma orientado a objetos, como:
› Classes, Objetos, Atributos e Métodos.
› Abstração.
› Encapsulamento.
› Polimorfismo.
› Herança.
Metas
Após o estudo deste capítulo, esperamos que você consiga:
» Descrever os principais conceitos utilizados no paradigma orientado a objetos.
21
22. Programação II
Capítulo 2 – Orientação a Objetos:
conceitos principais
Vamos conversar sobre o assunto?
No capítulo anterior, você viu que ao longo dos anos a programação buscou
uma constante evolução no intuito de facilitar a vida dos programadores. O progresso
na organização dos programas evoluiu. Porém, os sistemas também ficaram cada vez
mais complexos. Em particular, os paradigmas de programação até então falhavam
principalmente em relação à reutilização de código. Para enfrentar este problema, um
novo paradigma surgiu: a orientação a objetos. Vamos abordar neste capítulo os principais
conceitos relacionados a esta nova forma de organizar seus programas. Este capítulo é
fundamental para seu bom progresso no curso. Então, vamos aprender?
1. Introdução
Como vimos no capítulo anterior, a programação orientada a objetos surgiu
como uma nova abordagem para tratar a inerente complexidade dos novos sistemas. A
programação orientada ao objeto (object-oriented programming) pode ser considerada
como uma extensão quase natural da programação modular. Apesar de seus conceitos já
terem sido estabelecidos desde a década de 70, apenas em meados da década de 90 que
este paradigma começou a ganhar maior destaque na comunidade de desenvolvimento de
sistemas.
Seu uso enfatizou uma ideia que até então passava despercebida: a aproximação
com o mundo real. Na programação orientada a objetos temos a necessidade de buscar
as entidades de um domínio, onde através da qual um indivíduo observa a realidade
(domínio) e procura capturar sua estrutura (abstrair entidades, ações, relacionamentos)
com elementos que forem considerados relevantes para a descrição desse domínio. Com
as entidades de domínio identificadas, podemos utilizar as técnicas de abstração para
a composição das classes abstratas, construindo desta maneira um sistema facilmente
reutilizável nas suas estruturas internas.
A abordagem OO (orientada a objetos) enfatiza duas características: (i) reutilização
de código e (ii) modularidade. Nisto, OO é imbatível quando comparada com as metodologias
antigas. Em termos de modelo computacional podemos dizer que enquanto as metodologias
tradicionais utilizam o conceito de um processador, uma memória e dispositivos de I/O para
processar, armazenar e exibir as informações. A orientação a objetos emprega um conceito
mais real, mais concreto: o de Objeto.
Porém, além de objetos, existe uma série de outros conceitos que são
importantes para a compreensão do paradigma OO, representados na Figura 17. São eles:
encapsulamento, polimorfismo, herança, composição e abstração.
22
23. Programação II
Figura 17 – Os pilares do paradigma orientado a objetos
Vamos dar uma olhada agora como cada um destes conceitos contribui no
paradigma OO.
2. Abstração
Abstração é considerada a habilidade de modelar características do mundo real
do problema que o programador esteja tentando resolver. Ao mesmo tempo, para melhor
lidar com a modelagem de problemas complexos, este processo mental indica que se
deve ignorar detalhes não relevantes e focar nas características essenciais dos elementos
modelados. Com isso, uma realidade complexa pode ser representada em um modelo
simplificado, como os objetos no paradigma OO.
A abstração na descrição de objetos depende do ponto de vista e objetivo de
quem faz a descrição. Grady Booch, um atuante e renomado pesquisador na comunidade
científica, ilustra esta observação na sua frase: “Uma abstração depende mais do observador
do que do objeto observado”. Por exemplo, a visão de um apartamento por um engenheiro
civil tende a ser diferente da de um decorador de ambientes. O primeiro deve se preocupar
mais com os aspectos relacionados ao posicionamento de vigas de sustentação, colunas,
dentre outros. Já um arquiteto se preocuparia mais em como otimizar o espaço, maximizar
a circulação de ar, do melhor local para colocar mesas e cadeiras.
Estas diferentes visões ocorrem pelo diferente interesse que os dois profissionais
tem sobre a mesma entidade observada: o apartamento. O mesmo vale para a programação,
pois só devemos representar nos sistemas que vamos criar, aquelas características que nos
interessam dos objetos reais. Como exemplo, ao modelarmos um objeto avião no contexto
de um sistema de venda de passagens aéreas, não vai nos interessar a característica do
número de turbinas do avião, mas sim, seu número de assentos disponível.
Existe uma série de princípios que são utilizados por nós, humanos, para realizar
abstrações em nosso cotidiano. Vamos ver como alguns destes princípios e como eles se
aplicam no paradigma OO.
2.1. Classificação/Instanciação
Classificação é um mecanismo de abstração através do qual o ser humano percebe
a existência de um conjunto de fenômenos da realidade (elementos, interações) com
algumas características em comum e atribui um nome (conceito) a esse conjunto (classe) de
fenômenos. Já a instanciação é o processo inverso à classificação.
Na orientação a objetos, este princípio é utilizado para a identificação de classes e
objetos. As duas ações possíveis são:
» Classificar significa agrupar objetos com base em estruturas e comportamentos
semelhantes;
23
24. Programação II
» Instanciar objetos significa gerar novos exemplares a partir de uma descrição
abstrata de um objeto genérico.
Vejamos o exemplo na Figura 18, que ilustra estes processos no contexto de dois
estudantes, João e Maria. Ambos são indivíduos, com seus dados particulares, como seus
nomes. Porém, ambos podem ser agrupados no conjunto de indivíduos que representam os
estudantes.
Figura 18 – Classificação/Instanciação de objetos
A “semântica” ou o significado do mecanismo de classificação equivale a um
mecanismo de pertinência. Neste mecanismo, o fato de um objeto pertencer a uma
determinada classe pode ser melhor caracterizado se ocorrer uma relação do tipo “é um / é
uma”. Por exemplo, “Maria é uma estudante”.
A partir desta abstração, pode-se então melhor entender os conceitos de objetos e
classes, atributos e métodos.
Objetos representam entidades do mundo real, quer sejam estas concretas ou
abstratas, sobre as quais são armazenados dados e temos operações para manipulá-los.
Objetos também se caracterizam por ter uma identidade única, um comportamento e um
estado. A identificação de objeto deve ser única, uniforme e independente do conteúdo do
objeto. Isso implica que podem haver objetos com características semelhantes (como dois
martelos azuis ou dois funcionários com o mesmo nome), mas que são distintos.
Uma classe é abstração das características mais importantes de um grupo de
objetos semelhantes. Cada classe descreve um conjunto (possivelmente infinito) de objetos
individuais. A partir de uma classe podem ser gerados diferentes objetos e por isso, cada
objeto é dito ser uma instância de uma classe. Assim, cada instância de uma classe tem seus
próprios valores para cada dado que o caracteriza, mas também podem compartilhar dados
e comportamento com as outras instâncias da classe. Implicitamente, cada objeto contém
uma referência para sua própria classe -- em outras palavras, ele sabe o que ele é.
Existe hoje uma representação bastante difundida para representar graficamente
classes em um modelo orientado a objetos. Esta notação é definida por uma linguagem visual
chamada UML (do inglês, Unified Modeling Language). UML é utilizada para representação
de muitas etapas durante a análise de um sistema. Ela é formada por vários diagramas.
Um deles é o diagrama de classes, em que classes são representadas graficamente. Para
facilitar nossa explicação sobre os conceitos OO, utilizaremos a notação UML para descrever
as classes nesta disciplina. Em UML, uma classe é representada como descrito na figura a
seguir.
24
25. Programação II
Figura 19 – Notação UML para Classes
Nesta notação, há uma caixa retangular composta de três partes. A primeira parte
(localizada no topo do retângulo) define o nome da classe. Esta é única parte que deve existir
na notação UML para representar uma classe. A parte intermediária diz qual é a estrutura
da classe. Enquanto a parte inferior informa qual é o seu comportamento, através das ações
que objetos desta classe podem realizar. Para melhor exemplificar, a classe Estudante tem
como estrutura interna o nome do estudante e o curso ao qual ele está vinculado. Como
ações, é possível pedir para um objeto imprimir seu nome.
Cada objeto possui um conjunto de características ou informações que os
caracteriza, chamadas então de atributos. O conjunto de atributos também é chamado
de estrutura da classe. Por exemplo, a classe Estudante pode conter como sua estrutura
informações como o nome, sexo, data de nascimento, colégio ou faculdade, dentre outros.
Os atributos possuem valores, funcionando como variáveis. Por exemplo, o atributo nome
para um dos objetos da Figura 19 deve guardar o valor “João”. O conjunto de valores dos
atributos de um determinado objeto é chamado de estado. Como visto na Figura 19,
atributos (se existirem) são representados na parte intermediária de uma classe em UML.
O comportamento de um objeto é definido através de seus métodos. Métodos
definem as habilidades dos objetos. Em outras palavras, as ações que um objeto de uma
classe pode realizar. É comum serem chamados também de comportamento de uma classe
de objetos. Na maioria das vezes, métodos utilizam os atributos da classe como “matéria-
prima” para realizar suas ações, além de também poderem receber parâmetros, como em
um procedimento tradicional. Uma classe pode ter qualquer número de métodos, inclusive
nenhum. Por exemplo, para uma classe carro, poderiam ser definidas ações como andar,
frear, ligar o motor, dentre outras. Como visto na Figura 19, métodos (se existirem) são
representados na parte inferior de uma classe em UML.
Métodos são definidos nas classes, porém sua execução só ocorre quando o
método é invocado através do objeto. Por exemplo, pode haver uma classe que represente
um automóvel e dois objetos que representem dois modelos diferentes: um Fusca e uma
Ferrari. A classe define um método, que faz com que a velocidade do automóvel seja
reduzida. A utilização de um método frear() deve afetar apenas um objeto em particular.
Todos os carros podem frear, mas a ação em um objeto afeta apenas o objeto em questão.
Apesar da independência de cada objeto, para que um sistema funcione é
necessário que os objetos interajam. A interação entre objetos ocorre através da troca de
mensagens. Uma mensagem representa o ato de um objeto de uma classe chamar algum
método que outro objeto possua, fazendo com que este comportamento seja executado.
É comum esta ação ser chamada de ativação ou invocação de um método. Em analogia
com a programação estruturada, uma mensagem pode ser vista como uma chamada a
procedimento, inclusive com o retorno de um resultado a partir da execução deste método,
como ilustrado na Figura 20.
25
26. Programação II
Figura 20 – Ilustração do envio de uma mensagem para um objeto
Para um objeto invocar um método em outro objeto é necessário que obtenha uma
referencia ao objeto-alvo. Como cada objeto precisa ser identificado de forma unívoca (ou
seja, de forma exclusiva), faz-se então necessário definir um identificador para cada objeto.
Tome como exemplo a Figura 21. A classe Estudante define as características gerais de
um aluno. O método imprimirNome() é definido para apresentar tanto o nome do aluno,
quanto seu curso.
Figura 21 – Referências para objetos
Existem dois objetos que representam dois alunos distintos. Para isso, cada objeto
tem um identificador único. Para o aluno cujo nome é Fernando, o identificador do objeto é
aluno1, enquanto para o outro aluno, aluno2. Para se mandar uma mensagem para um dos
objetos utiliza-se a chamada “sintaxe do ponto” (dot syntax). Esta sintaxe indica que o acesso
a um atributo ou método de um objeto deve ser feita utilizando a forma “objeto.método”
ou “objeto.atributo”. No nosso exemplo, para chamar o método imprimirNome no objeto
aluno1, deve ser utilizada a notação aluno1.imprimirNome(). Dessa forma, representa-se a
ativação de uma mensagem neste objeto.
2.2. Agregação/Composição
A ideia de agregação ou composição estabelece a criação de novas classes de
objetos a partir da junção de classes já existentes. Essa ideia de composição é muito comum
para se representar relações do tipo todo-parte, como no caso de uma turma ser composta
por instâncias de outras classes, como um professor, um conjunto de alunos, dentre outros.
Da mesma forma que a classificação/instanciação são processos inversos, para a
composição de classes são definidas duas operações diferentes:
26
27. Programação II
» Operação de Agregação: Quando unimos um conjunto de objetos para formarmos
um novo objeto;
» Operação de Decomposição: Quando, analisando um objeto, isolamos cada um de
seus componentes.
Veja o seguinte exemplo:
Figura 22 – Agregação/Decomposição
Neste exemplo, a classe Automóvel pode ser decomposta nas classes Placa e Motor.
Este processo, chamado de decomposição, realiza uma análise sobre a classe Automóvel
e aplica um refinamento. Refinar significa deixar ainda mais claro a estrutura de um
determinado conceito. O processo inverso faz uma síntese de conceitos já existentes para
criar um novo conceito.
Há uma notação especial em UML para representar agregação, como ilustrado e
exemplificado na Figura 23. Um losango vazado é anexado à extremidade de um caminho de
associação ao lado do agregado (o todo) para indicar agregação.
Figura 23 – Notação UML para agregação; Tipos de Agregação
Há uma distinção entre dois tipos de agregação: a agregação compartilhada e a
composição. No primeiro tipo, representado pela ideia de uma disciplina que contem um
27
28. Programação II
professor e uma sala vinculada, objetos contidos podem existir sem serem parte do objeto
que os contém. Em outras palavras, um professor continuará a existir, mesmo que não haja
o conceito Disciplina.
No caso da composição, objetos contidos não fazem sentido fora do contexto do
objeto que os contém. No exemplo apresentado, um pedido contem itens e um Cliente
vinculado. Se você destruir o pedido, os itens são destruídos junto, pois eles não tem sentido
fora do pedido. Neste caso, o losango anexado à extremidade da classe agregadora deve ser
preenchido, e não vazado.
2.3. Associações Simples
As entidades envolvidas apresentam existências independentes, mas existe uma
ligação entre elas. A agregação não é única forma de relacionamento entre objetos e classes.
Classes e objetos podem formar associações, que consistem na descrição genérica de uma
ou mais ligações entre as classes, permitindo que objetos de uma classe utilizem recursos
de outros objetos. Ao se estabelecer associações entre classes, em geral se estabelece o
significado de uma classe “usar” ou “possuir” outra. Para representar associações entre
classes, UML utiliza uma linha unindo as duas classes. Alguns exemplos de associações são
descritas abaixo.
Figura 24 – Associações entre classes
Nos exemplos acima, temos que um cliente está vinculado a um produto, e
vice-versa. Uma conta possui um histórico de transações. Um hóspede está vinculado
à um quarto. Associações podem ainda ser refinadas inserindo a multiplicidade nos
relacionamentos entre classes. Por exemplo, um único cliente pode estar vinculado a zero
ou mais (sem limites) pedidos. Um velocista pode participar de zero ou mais corridas, e uma
corrida deve ter no mínimo dois e no máximo seis velocistas.
2.4. Encapsulamento
Um dos aspectos mais positivos da programação modular foi a ideia de
encapsulamento de informações. A programação orientada a objetos preserva este
conceito. Encapsular dados em OO consiste em separar os aspectos externos de um objeto,
os quais são acessíveis a outros objetos, dos detalhes internos de implementação do objeto,
os quais permanecem escondidos dos outros objetos. O uso de encapsulamento evita que
objetos possuam grandes dependências entre si, de modo que uma simples mudança em
um objeto possa trazer grandes efeitos colaterais e problemas para outros objetos.
Como vimos no capítulo I, o conceito de encapsulamento não é exclusivo da
abordagem de orientação a objetos. Entretanto, a habilidade de se combinar estrutura de
dados e comportamento em uma única entidade torna o encapsulamento mais elegante
e mais poderoso do que em linguagens convencionais que separam estruturas de dados e
comportamento.
28
29. Programação II
No paradigma OO, o encapsulamento institui conceitos e regras que ajudam a
promover a modularidade entre objetos. Para isso, o acesso a componentes de um objeto
é controlado e, especialmente, os atributos de um objeto só devem ser modificados pelos
métodos do próprio objeto. Veja como isso ajuda a proteger um programa. Imagine a classe
Conta que representa uma conta corrente, cujos atributos são seu número e seu saldo na
Figura 25.
Figura 25 – O benefício do encapsulamento
De acordo com projeto, o saldo de uma conta não pode ser menor que zero. Mas
da forma como até agora conhecemos classes, o acesso a atributos e métodos é liberado.
Logo, qualquer objeto poderia ter o atributo saldo acessado diretamente, e modificado para
um valor inválido, como representado no exemplo. Para sanar este problema, linguagens
orientadas a objetos oferecem mecanismo que modificam a visibilidade (escopo) de
atributos e métodos. É possível então estabelecer que todos os atributos de uma classe só
possam ser acessados de métodos definidos dentro da própria classe. É dito então que o
escopo deste método é privado à sua classe.
No nosso exemplo, a modificação do saldo de uma conta só poderá ser feita através
do método debitar(valor) que internamente estabelece regras que não permitem que o
saldo da conta fique negativo.
A ideia do encapsulamento faz também com que o sistema não dependa das
implementações internas das classes, mas sim de suas interfaces. A interface de um objeto é
representada pelo conjunto de todos os métodos que este objeto oferece a outros objetos.
A interface de um objeto é que é externamente visível, e que protege dados internos, além
de outras operações que não devem ser disponibilizadas ao mundo externo.
Imagine uma classe que represente o conceito de uma coleção de pessoas, e que
forneça métodos para manipular esta coleção, como inserir uma pessoa, consultar uma
pessoa pelo nome, etc. Internamente, esta classe poderia utilizar diferentes estruturas para
guardar a coleção de pessoas, como um vetor, uma lista dinâmica ou mesmo um banco de
dados. Porém, para quem vai utilizar algum objeto desta classe, isso não importa. O que
importa é que exista um método para inserir, outro para consultar, e assim por diante. Desta
forma, alterações dentro da classe não afetarão o restante do sistema. Por exemplo, pode-
se trocar a implementação interna da classe, mudando de um vetor para o banco de dados.
Se a interface for mantida, os objetos que usam não sofreram nenhum impacto, o que torna
a manutenção mais fácil.
Por estes motivos, na orientação a objetos é muita utilizada a expressão “programar
29
30. Programação II
para a interface”. Esta expressão indica a ideia que a interface de um objeto representa
um contrato entre uma classe de objetos. Enquanto o contrato for mantido, objetos que
dependam dos métodos de uma classe não serão prejudicados.
2.5. Generalização/Especialização (Herança)
Generalização e especialização são abstrações que permitem o reuso de objetos
através da criação de classes de objetos a partir de outras classes. No caso, a especialização
é o mecanismo pelo qual pode-se definir uma nova classe de objetos a partir de uma classe
já existente. Esta nova classe poderá aproveitar o comportamento e possíveis atributos
da classe estendida. A classe sendo refinada é chamada de superclasse ou classe base,
enquanto que a versão refinada da classe é chamada uma subclasse ou classe derivada. A
generalização é o inverso da especialização. A Figura 26 ilustra esta relação:
Figura 26 – Generalização/Especialização, a base para a Herança entre classes
Neste exemplo, a superclasse Estudante é refinada em dois tipos mais específicos, os
de graduação e os de pós-graduação. Diz-se que cada classe derivada herda as características
de sua classe base. Por conta disso é mais comum essa abstração serem tratadas em
linguagens de programação orientadas a objetos como Herança. No nosso exemplo, a classe
Estudante poderia ter como atributos: nome ou instituição vinculada, estes também são
atributos de estudantes de graduação ou pós-graduação. Cada classe derivada não apenas
herda as características de seu ancestral como também pode acrescentar seus atributos e
operações específicos, como o curso para o Estudante de Graduação.
Em UML, a notação para generalização é representada por uma associação entre
superclasse e subclasses, onde um triângulo é posto na extremidade próxima à superclasse
(Figura 27).
30
31. Programação II
Figura 27 – Representação UML para Herança; Exemplo para uma hierarquia de classes
É importante salientar que a herança não vale apenas para dados, mas também
para métodos. No mesmo exemplo, a classe Estudante define um método imprimeDados.
Portanto, as subclasses da classe Estudante também herdam este comportamento. Com
isto, embora o método não seja definido na classe EstudanteGraduação ou EstudantePos,
qualquer objeto de uma destas duas classes pode ter o método imprimeDados sendo
invocado.
A correta chamada de métodos ou o acesso a atributos em classes que
possuam uma relação de herança ocorre através da busca nas classes que participam da
hierarquia. Exemplificando: quando um método é chamado em um objeto de uma classe,
a especificação deste método é procurada na classe deste objeto. Se este método não é
encontrado, busca-se então na definição de sua superclasse, e assim por diante, pois uma
hierarquia de herança pode ser formada por várias classes, recursivamente aplicada a um
número arbitrário de níveis. A Figura 28 exemplifica uma relação de herança em mais de um
nível.
Figura 28 – Múltiplos níveis de herança
31
32. Programação II
Neste exemplo, o Estudante de Graduação além do curso, possui também como
atributos herdados a matricula (da classe Estudante) e o nome (da classe Pessoa).
Uma subclasse pode também sobrepor uma característica de sua superclasse,
o que significa que a nova característica local à subclasse irá substituir a característica de
sua superclasse. Neste caso, esta ação é chamada de sobreposição. Ainda no exemplo
anterior, a classe Estudante sobrepõe o método imprimeDados da classe Pessoa. Em outras
palavras, a execução deste método em objetos da classe Estudante obedecerá a esta nova
definição. Note que esta alteração também afeta objetos das classes EstudanteGraduação
e EstudantePos. Objetos dos tipos Pessoa e Empregado executam a definição original do
método.
A definição e uso de herança traz consigo também um princípio muito importante
para a orientação a objetos que é o princípio da substituição. Este princípio se baseia no
fato que, se uma subclasse herda métodos e atributos de sua superclasse, em qualquer
lugar de um programa que você possa utilizar uma instância de superclasse, você também
deve poder utilizar uma instância de qualquer subclasse desta classe. Embora pareça
complicado, imagine um programa que faz uma chamada ao método imprimeDados de
um objeto Pessoa, de acordo com a hierarquia de classes da Figura 27. Pelo princípio da
substituição, você pode substituir este objeto Pessoa por qualquer outro objeto que seja
instância de alguma subclasse de Pessoa, como Estudante, Empregado, EstudantePos ou
EstudanteGradução. Como todos eles vão possuir o método imprimeDados, o programa vai
continuar a funcionar sem problemas. Este princípio é muito útil para facilitar a extensão de
programas.
Herança Simples e Herança Múltipla
A herança pode ser de dois tipos: Simples e Múltipla. A herança simples é quando
uma classe é subclasse de somente uma superclasse, enquanto a herança múltipla ocorre
quando uma classe é subclasse de várias superclasses e, consequentemente, herda as
características de cada uma delas. Os exemplos que vimos até agora foram todos de herança
simples. A Figura 29 apresenta um exemplo de uma herança múltipla.
Figura 29 – Exemplo de Herança Múltipla
Neste caso, note que a classe VeiculoAnfibio possui duas superclasses, Carro e
Barco. Desta forma, ela herda métodos e atributos destas duas classes. Herança múltipla
não se limita a apenas duas classes, de acordo com o contexto do mundo que se queira
modelar. Porém, a herança múltipla traz um problema: o conflito de atributos ou métodos
herdados das superclasses. Em nosso exemplo, note que tanto Carro quanto Barco possuem
um método parar. Imagine então o que aconteceria se fosse feita uma chamada ao método
parar em um objeto do tipo VeiculoAnfibio. Qual implementação de parar deveria ser
chamada?
32
33. Programação II
Por conta dessa e de outras questões, a Herança Múltipla não é implementada em
todas linguagens de programação, como no caso de Java. Porém, pode ser encontrada em
outras linguagens como C++ e SmallTalk.
2.6. Polimorfismo
O termo polimorfismo é originário do grego, e etimologicamente quer dizer “muitas
formas”. Na orientação a objetos, isso significa que um mesmo tipo de objeto, sob certas
condições, pode realizar ações diferentes ao receber uma mesma mensagem. Ou seja,
objetos podem ter reações diferentes ao mesmo estímulo (Figura 30).
Figura 30 – Polimorfismo: Objetos têm diferentes reações a um mesmo estímulo
Polimorfismo permite o envio de uma mesma mensagem a objetos distintos, onde
cada objeto responde da maneira mais apropriada para a classe chamadora. No exemplo
da Figura 30, a mesma mensagem “desenhar” pode ser enviada a três objetos distintos: o
quadrado, o círculo e o triângulo. Os três sabem como tratar esta mensagem, porém cada
um realizará ações diferentes.
Existem dois tipos principais de polimorfismo: a sobrecarga e a sobreposição.
Na sobrecarga, uma mesma classe de objetos possui métodos com o mesmo nome,
porém quantidade ou tipo de parâmetros diferentes. No momento da chamada do método,
dependendo do parâmetro, um método ou outro será chamado. Por exemplo, a classe
Pessoa (Figura 31) possui dois métodos imprimir. O primeiro recebe apenas um parâmetro,
enquanto o segundo recebe dois.
Figura 31 – Sobrecarga de métodos
Já a sobreposição ocorre somente associada à herança. Quando um método
definido na superclasse não serve da forma original para a subclasse, ele tem de ser
redefinido. Como na redefinição a assinatura deve ser mantida, o novo método sobrepõe o
método definido na superclasse.
Veja como exemplo a Figura 32. Existem dois tipos de Funcionário: o que recebe
um salário fixo (Empregado) e outro cujo salário é calculado de acordo com suas vendas
33
34. Programação II
(EmpregadoComissão). O segundo tipo tem as mesmas características do primeiro, portanto
convém que se utilize herança para reaproveitar seus dados. Porém, o calculo de seu salário
é totalmente diferente. Neste caso, a subclasse modifica (sobrepõe) o comportamento do
método que calcula o salário para a classe EmpregadoComissão.
Figura 32 – Sobreposição de métodos
Neste caso, quando um objeto for do tipo Empregado, o calculo do salário obedecerá
a regra definida na superclasse. Mas quando o objeto for do tipo EmpregadoComissão, a
regra utilizada é aquela que foi redefinida nesta classe. Em resumo:
» O método a ser chamado é aquele pertencente ao objeto o qual a chamada está
relacionada;
» Caso o objeto não possua este método, a chamada é transferida para a superclasse,
e assim por diante até que o método seja encontrado;
Porém, ao contrário da sobreposição, métodos sobrepostos na subclasse precisam
ter a mesma assinatura de seus equivalentes na superclasse. Em outras palavras, precisam
ter o mesmo nome; o mesmo número de parâmetros e o mesmo tipo de parâmetros de
entrada e saída.
Exercícios
1. No contexto da programação orientada a objetos, o que é um objeto?
2. No contexto da programação orientada a objetos, o que é uma classe? Diga também
o que são métodos e variáveis de instância.
3. Indique qual é o principal benefício obtido quando os atributos de uma classe não
são visíveis a outras classes? Qual é o nome desta técnica de programação?
4. Explique o que é o relacionamento de herança entre classes.
5. Para você, qual o benefício do polimorfismo no paradigma orientado a objetos?
6. Utilizando orientação a objetos, indique quais seriam as classes, seus atributos,
métodos e associações para os seguintes sistemas:
a) Uma universidade;
b) Um acervo de mídias (CDs e DVDs)
c) Um filme.
34
35. Programação II
Vamos Revisar?
Neste capítulo, você foi apresentado a uma nova forma de organizar os conceitos
para criação de programas de computador. A orientação a objetos baseia-se em uma
aproximação da forma como os seres humanos organizam e realizam ações no mundo real.
Para isso, novas abstrações como classificação, agregação, associação, dentre outras são
fundamentais para que os programas sejam organizados de forma a permitir um maior reuso
de código e maior flexibilidade de manutenção. Conceitos como classes, objetos, métodos
e atributos são fundamentais em linguagens orientadas a objetos, vão fazer parte do dia a
dia deste curso. A partir deles, conceitos mais avançados como herança e polimorfismo são
gerados, e serão mais detalhados adiante nesta disciplina. Por enquanto, é importante que
os conceitos deste capítulo seja amadurecidos em sua cabeça.
35
36. Programação II
Capítulo 3
O que vamos estudar neste capítulo?
Neste capítulo, vamos estudar os seguintes temas:
» Linguagem de Programação Java
» Java Software Development Kit – JSDK
» O ambiente de programação JCreator
Metas
Após o estudo deste capítulo, esperamos que você consiga:
» Identificar as principais características da Linguagem Java;
» Instalar e configurar o Kit de desenvolvimento necessário para compilar e executar
seus programas em Java;
» Instalar o ambiente de programação JCreator;
36
37. Programação II
Capítulo 3 – A Linguagem Java
Vamos conversar sobre o assunto?
Nos capítulos iniciais deste volume você aprendeu sobre a necessidade de
melhorar a forma como organizamos os conceitos em linguagens de programação e como
a orientação a objetos busca melhorar o reuso e a manutenção de programas. Bem, até
agora, tudo foi bastante teórico. Então chegou a hora de começarmos a ver como na prática
isso tudo funciona. Para isto, este capítulo introduz a linguagem de programação orientada
a objetos mais popular no mundo: Java.
1. Introdução
Você Sabia?
A linguagem de programação Java tem origem no início da década de 90, dentro
da Sun Microsystems. Na época, uma equipe liderada por Patrick Naughton, Mike Sheridan,
e James Gosling tinha como objetivo projetar uma nova linguagem de programação capaz
de criar softwares capazes de comunicar diferentes dispositivos entre si. Originalmente,
batizou-se esta linguagem de Oak (carvalho, em inglês), em homenagem a um espécie de
árvore que os integrantes do projeto avistavam de sua sala.
Inicialmente a proposta de interoperabilidade de programas não foi muito bem
aceita pelo mercado, o que então fez com os objetivos fossem modificados. Na época, o Você sabia que a
linguagem Java tem
uso da Internet estava crescendo vertiginosamente. Foi então que a Sun viu nesta expansão esse nome devido a
uma oportunidade de usar Oak. A linguagem passaria então a ser voltada para construir um café?
aplicações para Web. A linguagem foi rebatizada e passou a se chamar Java, sendo Durante o projeto os
apresentada oficialmente em 1995. participantes tomava
muito café, e gostavam
Desde então, Java foi rapidamente adotada pelo mundo, mais rapidamente que principalmente de
qualquer outra linguagem de programação até então. Grandes fornecedores de tecnologia um proveniente da
como IBM, Oracle, Netscape, dentre outros, passaram a apoiar Java. ilha de Java. No meio
do projeto Green,
A linguagem evoluiu muito ao longo dos anos. Hoje, mas que uma linguagem, os projetistas foram
avisados que já havia
Java pode ser vista como uma plataforma de desenvolvimento e execução de programas
uma linguagem com
em diversas plataformas. Você encontra programas Java na Web, dos telefones celulares, o nome de oak, que
mainframes, cartões inteligentes, dentre outros. Com isso, a linguagem é dividida em 3 inicialmente era o
grandes segmentos: escolhido. Com isso, os
projetistas resolveram
1. JEE (Java Enterprise Edition): indicado para aplicações coorporativas, com homenagear a ilha que
produzia o café tão
necessidade gerenciamento de transações, balanceamento de carga, concorrência, apreciado por todos.
dentre outros;
2. JSE (Java Standar Editon): é o ambiente de desenvolvimento mais utilizado. Isso
porque seu uso é voltado a PCs e servidores, onde há bem mais necessidade de
aplicações;
3. JME (Java Micro Edition): é o ambiente de desenvolvimento para dispositivos
móveis ou portáteis, como telefones celulares e palmtops.
Hoje, estima-se que haja uma comunidade de mais de quatro milhões de
37
38. Programação II
programadores Java no mundo. Existem também grupos de usuários Java, chamados JUGs
(Java User Groups), que são utilizados para troca de ideias, dúvidas e melhores práticas no
uso da tecnologia Java. No Brasil, existem vários, como o DF-JUG (de Brasília) ou CE- JUG(do
Ceará). Existem diversos livros publicados sobre a linguagem, além de diversos ambientes
de programação e desenvolvimento.
2. Características da Linguagem
Durante seu projeto, a linguagem Java teve uma série de requisitos que podem se
apontados como fatores de seu sucesso.
1. Java é orientada a objetos: Java foi baseada no modelo de outras linguagens de
programação OO, no caso, Simula67 e SmallTalk. Com isso, os conceitos de herança,
polimorfismo e encapsulamento são implementados pela linguagem. A sintaxe e
seu sistema de tipos é bem parecido com C e C++, o que facilitou a migração de
programação de programadores já acostumados com estas linguagens;
2. Java é portável: Este conceito indica que um mesmo programa escrito em Java
pode ser executado em diferentes plataformas hardware ou sistema, sem grandes
ou nenhuma mudanças. Por conta desta característica, é comum se associar a Java
o seguinte jargão: “escreva uma vez, execute em qualquer lugar;
3. Java é segura, confiável e robusta: Ao contrário de suas predecessoras, Java não
trabalha com ponteiros. Em outras palavras, um programador Java não tem acesso
direto a posições de memória, um dos grandes problemas para quem trabalha
com linguagens como C ou C++. Java possui um mecanismo de tratamento de
exceções em que os erros que possam vir a ocorrer durante operações efetuadas
são tratadas paralelamente em outro trecho de código do sistema. Um erro que
durante a execução do programa poderá ser capturado sem imprevistos como, por
exemplo, acessos inválidos de memória do sistema operacional, ou do gerenciador
de arquivos, que podem acarretar problemas como o travamento, desligamento do
computador e perda de arquivos significativos;
4. Java promove reuso e facilita a manutenabilidade: Java possibilita a criação de
componentes reutilizáveis, ou seja, uma vez criadas funcionalidades para os
mesmos, estes poderão ser reutilizáveis posteriormente. Com isso é proporcionado
um baixo custo de produção e manutenção de códigos gerados com os demais
sistemas que venham a ser desenvolvidos;
5. Java é distribuída: Java possui a capacidade de compartilhamento de informações
possibilitando a distribuição de tal forma que haja processamento em máquinas
distintas. Possui integração com o protocolo TCP/IP que facilita a programação para
acessos remotos, utilizando protocolos como: HTTP e FTP;
6. Java permite a carga dinâmica de aplicações: Java oferece o recurso de que
aplicações chamadas applets possam ser carregadas remotamente e executadas no
contexto de um browser, ao ler uma página na Web. Ao fazer isso, Java introduziu
a interatividade na Web, permitindo que aplicações pudessem ser descarregadas
e utilizadas automaticamente por usuários, sem necessidade de instalação, ou
atualizações de versões. Ao mesmo tempo, applets possuem um criterioso modelo
de segurança que previne o acesso de aplicações remotas a possíveis ações danosas
no computador do usuário que rode um applet;
7. Java é livre e gratuita: Você não paga nada para criar um programa em Java. O
software para execução e construção de programas é disponibilizado gratuitamente
38
39. Programação II
na Internet. Além disso, existem vários ambientes gratuitos para facilitar a
construção e depuração de programas Java.
3. Máquina Virtual Java: o alcance de
portabilidade
Uma das características mais importantes da linguagem Java é a portabilidade. Para
isto, Java promove o conceito que nada na linguagem deve ser específico de uma plataforma
específica. O que na realidade Java faz, é criar a ideia de um computador abstrato sobre o
qual o programa deve ser executado. Esse computador abstrato é chamado de máquina
virtual Java (JVM).
Na realidade, a máquina virtual Java é uma especificação fornecida a início pela
Sun, e seu funcionamento une a ideia de compilação e interpretação de programas. Vamos
ver como isso funciona.
Para criar um programa Java precisamos criar sua especificação através da sintaxe
da linguagem. Como em praticamente toda linguagem de programação isso é feito através
da edição de um arquivo texto.
A partir de um programa escrito corretamente, a etapa que segue é a compilação do
programa para geração de sua versão executável. Aqui vem uma grande diferença. Em vez
de gerar um arquivo executável que é voltado para um computador ou sistema operacional
específico, Java gera uma versão intermediária do programa, o bytecode.
O bytecode é um arquivo neutro e independente de plataforma. Ele representa
o programa executável e livre de erros, como uma linguagem de máquina destinada a
um processador virtual especificado pela máquina virtual Java. Desta forma, qualquer
implementação de uma máquina virtual Java deve ser capaz de executar este programa. O
que de fato acontece, pois hoje existem implementações da máquina virtual Java para as
mais diversas plataformas, como Windows, Linux, MacOS, dentre outros (Figura 33).
Figura 33 – O processo de compilação e execução de um programa Java
A JVM utiliza o mecanismo de interpretação para executar os bytecodes que
correspondem ao programa originalmente escrito pelo programador. O uso de uma
máquina virtual acarreta em um tempo a mais na execução do programa, o que indica que
programas escritos em Java tendem a ser mais lentos que programas equivalentes escritos
em C ou C++. Porém, a facilidade na migração de um sistema entre diferentes plataformas é
um requisito cada vez mais importante na construção das aplicações modernas. Ou seja, é
um benefício que se paga.
Além disso, as atuais implementações de JVM fazem uma série de otimizações que
diminuem a diferença em relação à execução de um programa Java e programas compilados
diretamente para uma plataforma específica.
39