Prévia do material em texto
Programação Orientada a Objetos
honrando o paradigma (com códigos em Java)
Márcio Torres
Esse livro está à venda em http://leanpub.com/poocomhonra
Essa versão foi publicada em 2016-12-02
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools
and many iterations to get reader feedback, pivot until you have the right book and build
traction once you do.
© 2016 Márcio Torres
http://leanpub.com/poocomhonra
http://leanpub.com/
http://leanpub.com/manifesto
Conteúdo
Prefácio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ii
Abordagem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ii
Linguagem de programação usada nos exemplos e estudos de caso . . . . . . . . . . . iii
Organização dos capítulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iii
Para quem é este livro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iv
Para quem não é este livro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iv
Convenções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v
Sobre o Autor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vi
Capítulo 0 – Programação Modular . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Modularização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Modularização na Programação Procedimental . . . . . . . . . . . . . . . . . . . . . 2
Modularização na Programação Orientada a Objetos . . . . . . . . . . . . . . . . . . 10
Subprocedimentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Procedimentos e Dados Estruturados . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
“Procedimentos Orientados a Objetos”? O que é isso rapaz? . . . . . . . . . . . . . . 28
POO com Honra! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Capítulo 1 – Introdução à Programação Orientada a Objetos . . . . . . . . . . . . . . 32
Origem da POO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Classes e Objetos (Tipos e Instâncias) . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Construção (Instanciação e Inicialização) de Objetos . . . . . . . . . . . . . . . . . . 38
Representação string dos objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Validade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Estado dos Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Complexidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Referências . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Efeitos Colaterais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Comportamento (ações, operações, algoritmos, lógica) dos Objetos . . . . . . . . . . . 50
Mutabilidade e Imutabilidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Objetos de Valor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Objetos Entidades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Identidade dos Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Prefácio
Ministrar uma disciplina, ter estudantes e observar suas dificuldades, identificar as principais
armadilhas que capturam e “travam” esses aprendizes, isto te instiga, fomenta e torna-se o
principal motivador para um professor tornar suas aulas, exemplos, anotações, explicações, etc,
em um livro didático de educação profissional.
Esse livro começou em 2016, quando lecionava a disciplina de Programação Orientada a Objetos
(POO) para os cursos de Análise e Desenvolvimento de Sistemas e Técnico em Informática
para Internet. Nas primeiras aulas sempre trouxe anotações, que eram uma compilação das
bibliografias recomendadas, misturadas com experiência de trabalho e envoltas em um contexto
prático, já que os cursos de tecnologia e técnicos são voltados para a aplicação prática. O
desafio é achar a didática para obter uma abordagem pragmática, mas que ao mesmo tempo
ofereça o alicerce teórico, lembrando sempre que esses estudantes podem, no futuro, tanto
atuar profissionalmente, como procurar qualificação continuada, um mestrado por exemplo, ou
realizar concursos. Então essas anotações de aula foram desconstruídas e reconstruídas muitas
vezes para serem “ensináveis” e “aprendíveis”. O resultado foi um livro-texto da disciplina.
Sem mais delongas, este livro não foi escrito inicialmente para ser publicado, mas como um
material didático para usar em sala de aula. Contudo, pensando na utilidade às pessoas que
se esforçam em aprender POO, as vezes sozinhas ou em apoio seus cursos, acabei liberando
antecipadamente no leanpub. Se ele for só um pouquinho útil o trabalho já se pagou.
Se quiseresme dar um retorno, sugestões e críticas são bem-vindas, meu e-mail é:marciojrtorres@
gmail.com.
mailto:marciojrtorres@gmail.com
mailto:marciojrtorres@gmail.com
Introdução
Eu poderia dizer muita coisa aqui, mas vou ser pragmático e resumir o objetivo deste livro em
uma frase: utilizar o paradigma de programação orientado a objetos para construir software
de qualidade.
Abordagem
Este livro está sendo escrito a partir de notas de aula para a disciplina de Programção Orientada
a Objetos ministrada no curso de Análise e Desenvolvimento de Sistemas oferecido pelo Intituto
Federal de Educação, Ciência e Tecnologia do Rio Grande do Sul (IFRS). Essa origem dá ao livro
as seguintes características:
• Dedicado ao ensino: projetado para ser didático assim como as estratégias de ensino em
sala aula;
• Focado na prática: para cada explanação teórica há exemplos de aplicação prática, com
exemplos e estudos de caso, enfim, ele é usado na educação profissionalizante;
• No nosso idioma e na nossa cultura: sem cara de tradução, feito para as pessoas que
falam e/ou entendem português (com um leve sotaque gaúcho;)
• Linguagem acessível: tenta evitar expressões rebuscadas e formalidades linguisticas, o
que é muito útil para as pessoas não dormirem nas aulas – ou lendo um livro :)
O ponto forte do livro é a não-separação de teoria e prática, então cada conceito ou princípio
abordado virá junto com um exemplo “bobo”, um estudo de caso mais elaborado e um exercício.
Como exemplo “bobo” quero dizer uma amostra de implementação pequena e direta que não
é muito útil na “vida real”, mas que é muito esclarecedora para um aprendiz. Para ilustrar,
considere o exemplo canônico¹ de herança com a classe Animal e subclasses Gato, Pato,
Cachorro, enfim, é um recurso didático excelente para o primeiro o contato. O estudo de caso é
mais elaborado e serve para observar uma aplicação mais séria e plausível. Os exercícios servem
para a prática do conhecimento e apropriação da habilidade, é extremamente recomendado que
eles sejam realizados, seguindo as especificações e passar nos casos de teste quando houverem.
Falando em Especificações e Casos de Teste, os exemplos serão baseados em especificações e
assertivas que permitem validar e verificar se o software está saindo conforme o planejado e
com as respostas esperadas, que são premissas básicas de um controle de qualidade de software
(ver objetivo em Introdução).
¹um exemplo clássico e popular para algum tema é conhecido como Exemplo Canônico.
Introdução iii
Linguagem de programação usada nos exemplos e
estudos de caso
Os códigos estão escritos na linguagem de programação Java, que é a linguagem preferida
na grande maioria dos livros de referência em Programação Orientada a Objetos. Além disso,
Java é bastante usada pelas faculdades e escolas técnicas.Capítulo 0 – Programação Modular 28
14 public static void main(String[] args) {
15 String hashtag = "#poocomhonra";
16 System.out.println(corta(hashtag, 1).equals("#"));
17 System.out.println(corta(hashtag, 2).equals("#p"));
18 System.out.println(corta(hashtag, 3).equals("#po"));
19 System.out.println(corta(hashtag, 4).equals("#poo"));
20 System.out.println(corta(hashtag, 5).equals("#pooc"));
21 // faltam as situações excepcionais, por exemplo:
22 // System.out.println(corta(hashtag, 20).equals("#poocomhonra"));
23 }
24 }
https://git.io/
Esse procedimento traz alguns recursos importantes para o aprendizado, comomanipular strings
caractere a caractere para criar novas strings (ver linha 11).
O procedimento corta não trata as situações excepcionais
Considere melhorá-lo para que sejam tratados tamanhos além do comprimento da
string, como na última linha do código no exemplo anterior.
Desafio: implementar Texto.remover(String, char):String
Implementar um procedimento que recebe uma string e um caractere a remover que
retorne uma cópia da string sem o caractere informado. A seguir estão alguns Casos
de Teste:
Casos de Teste do procedimento para remover caracteres de uma string
System.out.println(remover("#poocomhonra", 'o').equals("#pcmhnra"));
System.out.println(remover("P O O", ' ').equals("POO"));
“Procedimentos Orientados a Objetos”? O que é isso
rapaz?
É possível implementar procedimentos que recebam objetos, já que objetos são Estruturas de
Dados e podem substituir os vetores nessa tarefa. No fim, é um misto: usa-se o procedimento
para codificar a lógica e o objeto para carregar os dados. Considere o exemplo da data revisitado,
agora com objetos, a seguir:
https://git.io/
Capítulo 0 – Programação Modular 29
Procedimentos misto com Objeto de dados para data
class DadosData {
int dia, mes, ano;
}
class DataMisto {
static int[] diasMes = {31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31};
static DadosData amanha(DadosData data) {
DadosData amanha = new DadosData();
amanha.dia = data.dia + 1;
amanha.mes = data.mes;
amanha.ano = data.ano;
if (amanha.dia > diasMes[amanha.mes - 1]) {
amanha.dia = 1;
if (amanha.mes diasMes[amanha.mes - 1]) {
26 amanha.dia = 1;
27 if (amanha.mesde valor e entidades;
• o que é a identidade de um objeto e como definir igualdade entre instâncias.
Origem da POO
As primeiras bases da Programação Orientada a Objetos (POO) surgiram no início da década
de 60, com noções de instâncias e objetos sendo discutidas no MIT e na Norwegian Computing
Center emOslo. A primeira linguagem de programação a introduzir o conceito de classes, objetos
e herança foi a Simula 67, projetada por Kristen Nygaard e Ole-Johan Dahl. Simula, como o nome
revela, era usada para modelar e resolver problemas através da simulação; A inception era que
coisas ou fenômenos do mundo real poderiam ser objetos no sistema, como objetos de chão-de-
fábrica, máquinas e materiais brutos, sistemas planetários e dinâmica de corpos celestes, etc.
https://pt.wikipedia.org/wiki/Simula_67
Capítulo 1 – Introdução à Programação Orientada a Objetos 33
Ainda na década de 60, a mais notável aplicação do conceito de objetos foi na apresentação de
objetos visuais interativos em interfaces com o usuário, como janelas, botões, pastas, documentos,
etc (sim, já existia naquela época) e vemos essas marcas conceituais inclusive nos Sistemas
Operacionais atuais⁶. O estudo desse tipo de interface resultou em softwares gráficos interativos
como o SketchPad, escrito por Ivan Sutherland. Ainda não era a nível de linguagens de
programação, mas o pensamento de que coisas, objetos, domundo real poderiam ser introduzidas
e representadas em sistemas também como objetos, só que virtuais, ganhava força e inspirava
novas ideias.
Então, Alan Kay, com o intuito de desenvolver um sistema interativo gráfico e pensando que a
linguagem Simula oportunizava a criação de aplicativos com mais facilidade, propos ao Xerox
PARC o desenvolvimento de, segundo ele, um computador pessoal para crianças de todas as
idades. No começo da década de 70, Alan Kay e sua equipe criam este computador, que foi
chamado de The Dynabook. Ele nunca foi produzido comercialmente, mas deixou um legado que
influencia a indústria de software até hoje: a computação pessoal e, principalmente, a linguagem
de programação criada para programá-lo: Smalltalk.
Uma pessoa inspiradora (não, não é o Steve Jobs)
O Dynabook, que Alan Kay idealizou na década de 70, foi a base conceitual para laptops e
tablets. Ao mesmo tempo foi ele quem demonstrou a viabilidade das interfaces gráficas. Tudo
isso viria então a ser comercializado por corporações e marketeiros, mas o cara da inception,
geralmente não é visto ou lembrado. Tem pessoas que não inspiram pelo que falam, mas
pelo que fazem. Contudo, Alan Kay fala muito bem e tem ideias (e ideais) muito nobres.
Sua intenção com o Dynabook era permitir que crianças o usassem para aprendizado, um
vídeo raro e fantástico pode ser visto aqui (EN) http://youtu.be/r36NNGzNvjo e, quando
tiveres 20 minutos do teu tempo, assista a TED Talk Uma Poderosa Ideia Sobre Ideias:
https://www.ted.com/talks/alan_kay_shares_a_powerful_idea_about_ideas?language=pt-br.
Smalltalk foi uma linguagem revolucionária, inspirada em Simula 67 ela elevava o patamar
da metáfora de Objetos em um sistema e solidificou o que se conhece hoje por Programação
Orientada a Objetos. Emmeio a diversas outras linguagens procedimentais, matemáticas, lógicas,
etc, (caretas, por assim dizer), Smalltalk oxigenava as ideias e provocava o raciocínio do que
era modelar e escrever programas por uma perspectiva diferente. Smalltalk foi pouco usada na
prática em comparação com outras linguagens da época, mas acabou por influenciar jovens
cientistas da computação e profissionais de tecnologia, mostrando que a POO era viável, que
funcionava e era eficiente.
Então, na década de 80, no Bell Labs, Bjarne Stroustrup cria a linguagem C++, que foi a primeira
linguagem de programação orientada a objetos a ser usada amplamente. Bjarne inspirou-se em
Simula e criou C++ em cima da linguagem C⁷. Um fato curioso é que a linguagem C++ era
originalmente chamada de C com Classes. C++ é um sucesso comercial, sistemas operacionais,
⁶Se quiseres conhecer um pouco mais da história das Graphical User Interfaces (GUI’s) considere este excelente artigo
disponível na ARS Technica (EN) A History of the GUI http://arstechnica.com/features/2005/05/gui/ ou então GUI Timeline
(EN) na ToastyTech http://toastytech.com/guis/guitimeline.html.
⁷C é uma linguagem procedimental criada em 1972 e usada até hoje (o ++ em C++ é uma analogia a um incremento do C).
http://youtu.be/r36NNGzNvjo
https://www.ted.com/talks/alan_kay_shares_a_powerful_idea_about_ideas?language=pt-br
http://arstechnica.com/features/2005/05/gui/
http://toastytech.com/guis/guitimeline.html
Capítulo 1 – Introdução à Programação Orientada a Objetos 34
firmwares, drivers, softwares de sistema, jogos, etc, até vírus, são escritos em C/C++ por ser
uma linguagem muito eficiente. Além da utilidade prática, um dos maiores legados de C++ e
Bjarne Stroustrup foi o de novamente demonstrar a viabilidade do paradigma de POO e inspirar
o desenvolvimento de novas linguagens, como Java e C#, por exemplo.
Duas empresas que revolucionaram a tecnologia (não, não é Apple
nem a Google)
O sonho de um pesquisador com ideias revolucionárias (ou sonhos psicodélicos) é encontrar
uma empresa que abrace e financie seu trabalho. Não é qualquer uma que investe em inovação
(de verdade), a maioria prefere “o mais do mesmo” ou o “facelift”. Para a evolução da
computação, a história passa pela Xerox PARC e Bell Labs. O Palo Alto Research Center
da Xerox tem uma contribuição memorável para a tecnologia da computação e sistemas de
hardware. Além da linguagem Smalltalk, a Xerox PARC foi responsável pela impressão a
laser, avanços em semicondutores, protocolo Ethernet e foi pioneira na computação pessoal
e interfaces gráficas - engenheiros de interfaces da época foram para a Apple. Os laboratórios
Bell (Bell Labs) nasceram como uma divisão da Companhia de Telefones e Telégrafos (AT&T)
em 1925. Foi responsável por inúmeros avanços tecnológicos, onde alguns são a base das
tecnologias da informação e comunicação atuais, tais como:
• Transistor: em 1956, Bardeen, Brattain e Schokley foram agraciados com o Prêmio Nobel
por inventar os primeiros transistores;
• Linguagem C: desenvolvida por Dennis Ritchie em 1972;
• Unix: Ken Thompson e Dennis Ritchie ganham o Prêmio Turing por suas teorias sobre
sistemas operacionais e o desenvolvimento do Unix;
• Linguagem C++: desenvolvida por Bjarne Stroustrup;
• CCD: o Charge-coupled device, responsável por exemplo por capturar luz e digitalizar
imagens, equipando câmeras digitais.
Existem tantas contribuições do Bell Labs que é impossível fazer uma lista justa, se tens um
tempinho e consegues ler um material em inglês fica a sugestão: http://www.nytimes.com/
2012/03/20/books/the-idea-factory-by-jon-gertner.html.
No começo da década de 90, uma equipe da Sun Microsystems⁸ liderada por James Gosling
desenvolvia uma linguagem, inspirada em Smalltalk e C++, porém mais “versátil”, o nome dela
era Oak (Carvalho) - isso mesmo, e a equipe era conhecida como “Green Team” e a linguagem
antes fora chamada de Greentalk. Antes de se chamar Java, Oak era projetada para ser usada
em dispositivos embarcados, mirando especialmente em set-top boxes (algo parecido com os
equipamentos da Sky e Net com um software interativo). Depois de tantos nomes elencados, o
nome Java foi escolhido em homenagem ao primeiro café produzido na ilha de Java na Indonésia
(o porquê de programador gostar tanto de café é uma outra história). A Java (mas que chamamos
“O Java”) foi lançada em 1995 e viria a ser uma das linguagens orientadas a objetosmais populares
⁸A Sun Microsystems foi fundada em 1982 e sua atividade principal era a fabricação de processadores e distribuição de
workstations e servidores, contudo também investia em sistemas operacionais, linguagens de programação e outros softwares.
Em 2009 foi vendida para a Oracle Corporation, junto com todas as suas tecnologias incluindo, claro, a linguagem Java.
http://www.nytimes.com/2012/03/20/books/the-idea-factory-by-jon-gertner.htmlhttp://www.nytimes.com/2012/03/20/books/the-idea-factory-by-jon-gertner.html
Capítulo 1 – Introdução à Programação Orientada a Objetos 35
e influentes da computação moderna, o que em grande parte se deu pela simultaneidade do
crescimento da Internet e das aplicações Web. Hoje ela é usada especialmente em aplicações
corporativas (Java EE) e para Smartphones (Android).
Classes e Objetos (Tipos e Instâncias)
A POO permite descrever com código os objetos do mundo real, com um número restrito
de detalhamento - atividade conhecida como abstração. Existem diversas formas de realizar
essa abstração, mas geralmente começa com uma observação do objeto em questão, suas
características e funcionalidades para então escrever uma definição.
Se tu nunca programaste com base em objetos isso pode parecer bastante confuso. Geralmente é
mais simples começar a projetar objetos pensando apenas em suas características.
Considere um livro e suas características relevantes; a definição de quais dados de um livro
seriam relevantes é algo bem subjetivo e depende do sistema que se está desenvolvendo, então
começaremos com um estudo de caso bem simples: considere um livro com título, autor e número
de páginas; após essa definição o próximo exercício é pensar em alguns exemplos, pode ser Livro
título: Neuromancer, autor: William Gibson, páginas: 312 e Livro título: Fortaleza Digital, autor:
Dan Brown, páginas: 197. Com esse estudo de caso é possível fazer a analogia entre classes,
atributos e objetos:
• Classe: é a definição do tipo⁹ do objeto, neste estudo de caso é o Livro;
• Atributo: é a definição de cada característica esperada em objetos de certo tipo/classe, neste
estudo de caso são os atributos título, autor e páginas;
• Objeto: é um exemplar (chamaremos de instância) de um certo tipo/classe e os valores de
seus atributos (chamaremos de estado), neste estudo de caso são os livros Neuromancer de
William Gibson com 321 páginas e Fortaleza Digital de Dan Brown com 197 páginas.
Revisando, Livro é a classe, titulo é um atributo,Neuromancer deWiliam Gibson com 321 páginas
é um objeto, uma instância da classe Livro com os seguintes valores de titulo, autor e páginas:
Neuromancer,William Gibson e 321.
A definição (implementação) da classe Livro e seus atributos “no Java” está a seguir:
⁹na prática estamos falando de classe e tipo como sendo a mesma coisa, mas na teoria tipo é uma definição mais abstrata
e elementar de uma informação.
Capítulo 1 – Introdução à Programação Orientada a Objetos 36
Implementando a classe Livro
1 class Livro {
2
3 String titulo;
4 String autor;
5 int paginas;
6
7 }
https://git.io/
As classes (linha 1) são definidas com class Nome, por exemplo class Cliente, class
Produto, etc. Os atributos (linha 3, 4 e 5) são definidos como Tipo nome, por exemplo String
endereco, double valorFinal, int quantidadeEstoque. Note que a definição de classes
implica em um novo Tipo, por exemplo em Livro meuLivro a variável meuLivro é do tipo
Livro (além de ser da classe Livro).
Implementação mínima
Importante esclarecer a classe Livro é uma implementação mínima, vários detalhes da
linguagem ficaram de fora, como a declaração de métodos, construtores, configuração
de visibilidade, etc.
Até aqui, implementamos apenas a definição de Livro, faltam as instâncias, em outras palavras,
os Livros de fato, os objetos do tipo Livro, que no estudo de caso eram oNeuromancer e Fortaleza
Digital. Para implementar o estudo de caso vamos criar uma classe principal Main que contém
um método principal main a seguir:
Instanciando objetos da classe Livro
1 class Main {
2 public static void main(String[] args) {
3
4 Livro liv1 = new Livro();
5 liv1.titulo = "Neuromancer";
6 liv1.autor = "William Gibson";
7 liv1.paginas = 321;
8
9 Livro liv2 = new Livro();
10 liv2.titulo = "Fortaleza Digital";
11 liv2.autor = "Dan Brown";
12 liv2.paginas = 197;
13
14 System.out.println("Livro " + liv1.titulo
15 + " de " + liv1.autor
16 + " com " + liv1.paginas
17 + " páginas");
18
19 System.out.println("Livro " + liv2.titulo
20 + " de " + liv2.autor
21 + " com " + liv2.paginas
https://git.io/
Capítulo 1 – Introdução à Programação Orientada a Objetos 37
22 + " páginas");
23 }
24 }
https://git.io/
Os livros são instanciados nas linhas 4 e 9. A palavra-chave, em Java, para construir um objeto
é new com a sintaxe new NomeDaClasse() – não esqueça dos parenteses, ele determinam o
construtor, que será visto adiante. Nenhum objeto existirá enquanto não for feito um new, lembre
disso. Neste exemplo, temos dois livros (dois new’s, dois objetos, duas instâncias) armazenados
nas variáveis liv1 e liv2. As variáveis oferecem o acesso aos atributos do objeto Livro, como
titulo, autor e paginas, que são atribuídos nas linhas 5, 6, 7, 10, 11 e 12 e lidos nas linhas 14
a 22. Compilando e executando essa é a saída que temos:
Compilando e executando
javac Livro.java Main.java; java Main
Livro Neuromancer de William Gibson com 321 páginas
Livro Fortaleza Digital de Dan Brown com 197 páginas
Embora o exemplo seja bem simples ele já explora alguns conceitos básicos de OO:
• o que é uma classe (ou tipo): Livro é uma classe que descreve um livro no sistema;
• o que é um objeto e instâncias: a cada new em Livro um livro é instanciado;
• o que é um atributo: titulo é um atributo dos objetos da classe Livro;
Classes e seus membros
Tecnicamente, os atributos presentes na classe Livro são conhecidos como variáveis
membro ou fields (campos) e eles podem ser da classe (variável da classe) ou da instância
(variável da instância). No exemplo titulo, autor e paginas são variáveis/campos
de instância. Membros da classe são definidos quando precedidos pela palavra-chave
static, que será visto adiante.
Exercício: escreva a classe para definir um filme
Considere, assim como no exemplo do livro, um exemplo com os dados (atributos)
básicos de um filme. Instancie alguns objetos, preencha-os e “printe-os”.
Um objeto não é formado apenas de atributos
É importante que entendas que este exemplo ainda está bem incompleto, um objeto
não é formado apenas de atributos, faltam, ainda, as operações do objeto, em outras
palavras, falta definir o que o objeto pode fazer, que é escrito como métodos. Os
atributos definem os dados e os métodos definem os algoritmos de manipulação desses
dados.
https://git.io/
Capítulo 1 – Introdução à Programação Orientada a Objetos 38
Construção (Instanciação e Inicialização) de Objetos
Retomando, nós definimos através das classes quais atributos queremos que os objetos, deste
tipo, tenha. Bem, dependendo do tipo do atributo ele recebe um valor default, isto é, um valor
(ou ausência) padrão, por omissão, em outras palavras, se instanciarmos um objeto os atributos
terão um valor inicial.
Atributos de tipo primitivo (int, long, float, double, boolean, por exemplo) recebem o valor
0 (false quando boolean). Atributos de tipo complexo (Objeto) não recebem valor, o que é
conhecido por null.
null não é vazio ou 0!
Um entendimento incorreto, comum aos iniciantes, é pensar o null como vazio,
quando na verdade null é a ausência de valor, ele é a incerteza. Por exemplo, considere
o atributo String email e Integer nroDependentes em um cadastro qualquer:
não atribuir valor a email significa que o e-mail não foi informado assim como não
atribuir um valor a nroDependentes, que significa que não sabemos se existem ou
não dependentes, não pode-se afirmar 0, ou seja que não há dependentes. O caso da
String é mais complexo, atribuir, por exemplo, email = ""; significa, a rigor, que
foi informado um e-mail, afinal ele é não-nulo. Muitas confusões de nulo e não-nulo
estão por vir, na vida de acadêmico e profissional, tente entender como lidar com ele.
Para exemplificar vamos a um primeiro estudo de caso. Considere uma classe Coordenada
com os atributos latitude:double e longitude:double. Note que, neste exemplo, uma
Coordenada baseada nos valores default resultaria na posição 0, 0 que é válida, porém inútil¹⁰.
Então as coordenadas teriam que ser inicializadascom valores úteis de latitudes e longitudes.
Vamos começar com uma classe Coordenada sem construtor definido:
Classe Coordenada sem construtor definido
class Coordenada {
double latitude;
double longitude;
}
https://git.io/
O código no exemplo é uma definição mínima da classe Coordenada, sem um construtor. Se não
definirmos um construtor, ainda assim existirá um construtor padrão implícito, sem parâmetros,
para podermos instanciar objetos dessa classe. Essa classe funciona como está, mas não é prática,
para criar uma coordenada no Chuí, bem na fronteira (-33.692627, -53.455263), teríamos que fazer
o seguinte:
¹⁰experimente digitar 0,0 no maps.google.com
https://git.io/
Capítulo 1 – Introdução à Programação Orientada a Objetos 39
Criando uma Coordenada sem construtor personalizado
Coordenada chui = new Coordenada(); // construtor padrão
chui.latitude = 3.692627;
chui.longitude= -53.455263;
System.out.println("Chui " + chui.latitude + ", " + chui.longitude);
Para tornar a criação de coordenadas mais prática vamos definir um construtor para a classe
Coordenada. Isso fará com que, para criar uma coordenada, tenhamos que informar latitude e
longitude já na construção. Segue a segunda versão da classe Coordenada:
Classe Coordenada com um construtor explícito
1 class Coordenada {
2
3 double latitude;
4 double longitude;
5
6 Coordenada(double latitude, double longitude) {
7 this.latitude = latitude
8 this.longitude = longitude;
9 }
10
11 }
https://git.io/
O código entre as linhas 6 e 9 determinam o construtor da classe Coordenada, que é usado para
instanciar coordenadas. Importante, o construtor especifica uma obrigação, isto é, para criar
uma coordenada somos obrigados a informar latitude e longitude, somos obrigados a inicializar
o objeto, a construção vazia não é mais permitida. As atribuições nas linhas 7 e 8 fazem a
passagem dos parâmetros latitude e longitude para os atributos latitude e longitude,
a diferença está na palavra-chave this que precede um atributo e serve para acessar a instância
estando dentro dela mesma (se te deu ummindblown agora não se preocupe, há muito o que ver
do this). O resultado prático é o uso da coordenada assim:
Criando uma Coordenada com um construtor personalizado
Coordenada chui = new Coordenada(3.692627, -53.455263);
System.out.println("Chui " + chui.latitude + ", " + chui.longitude);
Aprimeira linha demonstra o construtor sendo invocado com os dois argumentos (3.692627, -
53.455263), eles inicializam o objeto. Nesta segunda versão da classe Coordenada é impossível
construir uma coordenada assim new Coordenada(), com o construtor vazio, logo, mesmo
que queiramos uma 0, 0 teríamos que construir assim new Coordenada(0, 0). Para voltar
a permitir a construção sem inicialização podemos adicionar um segundo construtor sem
parâmetros (sim, as classes podem ter vários construtores, desde que eles tenham parâmetros
diferentes). Segue a terceira versão:
https://git.io/
Capítulo 1 – Introdução à Programação Orientada a Objetos 40
Classe Coordenada com dois construtores
1 class Coordenada {
2
3 double latitude;
4 double longitude;
5
6 Coordenada(double latitude, double longitude) {
7 this.latitude = latitude
8 this.longitude = longitude;
9 }
10
11 Coordenada() {
12 // ou this(0, 0), ou nada
13 this.latitude = 0;
14 this.longitude = 0;
15 }
16
17 }
https://git.io/
Entre as linhas 11 e 15 está o construtor vazio. Note que nem seria necessário inicializar os
atributos com 0, que já é o valor default. Ainda, como opção mais sofisticada, poderia passar a
coordenada 0,0 para o construtor completo com this(0, 0).
https://git.io/
Capítulo 1 – Introdução à Programação Orientada a Objetos 41
Exercício: implemente uma classe para representar horarios
que disponibiliza um construtor para inicializar horas,minutos
e segundos
Este exercício oferece uma definição fechada, nele eu ofereço os casos de teste, que
especificam o nome da classe e atributos, assim como os construtores e, o mais
importante, a sequência de interação e os resultados esperados (conhecido como API).
Neste caso, falta sua parte: a implementação, de modo que passe nos casos de teste.
Por exemplo, espera-se uma classe com o nome Horario e um atributo segundos, um
construtor vazio e outro com três parâmetros, etc. Então, faça cumprir os seguintes
casos de teste:
Casos de Teste
1 class Teste {
2 public static void main(String[] args) {
3 Horario h1 = new Horario();
4 System.out.println(h1.horas); // 0
5 System.out.println(h1.horas == 0); // true
6 System.out.println(h1.minutos); // 0
7 System.out.println(h1.minutos == 0); // true
8 System.out.println(h1.segundos); // 0
9 System.out.println(h1.segundos == 0); // true
10
11 // 13:45:16
12 Horario h2 = new Horario(13, 45, 16);
13 System.out.println(h2.horas == 13); // true
14 System.out.println(h2.minutos == 45); // true
15 System.out.println(h2.segundos == 16); // true
16 System.out.println(
17 h2.horas + ":" + h2.minutos + ":" + h2.segundos
18 ); // 13:45:16
19 }
20 }
https://git.io/
Exercício: projete e implemente uma classe para representar
frações
Este exercício, ao contrário do anterior, é aberto. Deves trabalhar a especificação
e a implementação, definir nome da classe, tipos dos atributos, construtor(es) e,
principalmente, escrever os casos de teste.
Representação string dos objetos
Nos exemplos dos tópicos anteriores, as instâncias (os objetos) de livro e coordenada eram
impressas assim:
https://git.io/
Capítulo 1 – Introdução à Programação Orientada a Objetos 42
Imprimindo os objetos
Livro livro = new Livro();
livro.titulo = "Fortaleza Digital";
livro.autor = "Dan Brown";
livro.paginas = 197;
System.out.println("Livro " + livro.titulo
+ " de " + livro.autor
+ " com " + livro.paginas
+ " páginas");
Coordenada chui = new Coordenada();
chui.latitude = 3.692627;
chui.longitude= -53.455263;
System.out.println("Chui " + chui.latitude + ", " + chui.longitude);
Imprimir um objeto é uma atividade comum, seja com uma formatação agradável ou até mesmo
para observar o estado de um objeto e debugar (resolver falhas). As linguagens, geralmente,
disponibilizam um método ou convenção para representar textualmente objetos (torná-los uma
string). Na linguagem Java este método é chamado de toString declarado na forma (com
a assinatura) @Override public String toString() { ... }. Por exemplo, considere a
classe Livro com o método toString implementado:
Classe Livro com toString implementado
1 class Livro {
2
3 String titulo;
4 String autor;
5 int paginas;
6
7 @Override public String toString() {
8 return "Livro " + this.titulo
9 + " de " + this.autor
10 + " com " + this.paginas
11 + " páginas";
12 }
13 }
https://git.io/
O código entre as linhas 7 e 12 implementa a representação texto (string) do livro. A instrução
System.out.println(v), sempre que v for objeto (não primitivo), executa ométodo toString
tornando isso possível:
https://git.io/
Capítulo 1 – Introdução à Programação Orientada a Objetos 43
Utilizando a representação string de Livro
1 class Main {
2 public static void main(String[] args) {
3 Livro livro = new Livro();
4 livro.titulo = "Fortaleza Digital";
5 livro.autor = "Dan Brown";
6 livro.paginas = 197;
7
8 System.out.println(livro);
9 }
10 }
https://git.io/
A instrução System.out.println(livro) na linha 8 executa como se fosse System.out.println(livro.toString());,
não precisando compor a descrição do livro no main. A execução do Main fornece essa saída:
Verificando o toString
javac Livro.java Main.java; java Main
Livro Fortaleza Digital de Dan Brown com 197 páginas
Sempre ofereça uma representação string dos seus objetos
A recomendação é que sempre seja sobrescrito o método toString. Dizemos so-
brescrito pois o método toString é herdado da superclasse Object (essa relação de
superclasse e subclasse é vista adiante).
Implemente a representação texto (toString) de Coordenada
Faça a seguinte instrução imprimir a coordenada:
Verificando o toString
Coordenada chui = new Coordenada();
chui.latitude = 3.692627;chui.longitude= -53.455263;
System.out.println("Chui " + chui);
// deve imprimir: Chui 3.692627, -53.455263
Sobrecreva o toString em seus exercícios anteriores (horarios e
fracoes)
Use os exercícios do tópico anterior para implementar toString e realize as verifica-
ções.
https://git.io/
Capítulo 1 – Introdução à Programação Orientada a Objetos 44
Validade
Estado dos Objetos
Um dos conceitos mais importantes de OO a entender é a definição de Estado. No tópico anterior
foi usado o exemplo de um livro com as informações do título, autor e número de páginas. Foi um
bom estudo de caso para exemplificar atributos, mas não é suficiente para exemplificar estado.
Não quer dizer que o livro não tenha estado, mas considerando o livro Neuromancer de William
Gibson com 321 páginas qual a chance de mudança do título, autor ou número de páginas? Quer
dizer, o livro tem estado, que são os valores do titulo, autor e paginas, mas ele não é propenso a
mudar no tempo.
Estado é o valor atual de todos os atributos do objeto
Esse é um entendimento simples e direto de estado. Tu podes ler como o livro
Neuromancer está com 321 páginas (embora neste exemplo o entendimento é de que
o livro tem 321 páginas).
Para ampliar o entendimento de Estado precisaremos de um estudo de caso mais complexo,
neste caso, um objeto que tenha certas características que mudam no tempo, então considere
uma aparelho de TV. Claro, vamos simplificar (abstrair) bastante, então considere a definição de
um aparelho de TV de certo modelo que suporta canais UHF e permite ajuste do volume, e só!. O
modelo da TV não muda, mas outros atributos mudam: ela pode estar ligada ou não, pode estar
em um canal de 14 a 69 (canais UHF) e um volume, digamos, de 0 a 100. A seguir o primeiro
esboço da TV com a classe TV:
Primeira versão da classe TV
1 class TV {
2
3 String modelo;
4 // a TV, por padrão, está desligada
5 boolean ligada = false;
6 // inicia no canal 14
7 int canal = 14;
8 // inicia com o volume 10
9 int volume = 10;
10
11 }
https://git.io/
Esta implementação funciona, mas não considera as regras de validade da TV, por exemplo, o
canal não pode ser menor que 14 ou maior que 69, então esse tipo de instrução não deveria ser
possível:
https://git.io/
Capítulo 1 – Introdução à Programação Orientada a Objetos 45
Invalidando a TV
TV tv = new TV();
System.out.println(tv.modelo); // TV sem modelo definido (NULL)
tv.canal = 12; // canal inválido
System.out.println(tv.canal); // canal mínimo 14
Para programar essa TV honrando o paradigma orientado a objetos temos que fazer algumas
alterações que consideram as regras de operação da TV :
• Tornar a informação do modelo obrigatória: exigir o modelo na construção do objeto, isto
é, definir um construtor ;
• Evitar que os atributos tenham valores inválidos: esse requisito é atingido por evitar o
acesso direto aos atributos. Também implica em criar métodos para operar a TV e consultar
o estado.
Segunda versão da classe TV
1 class TV {
2
3 private String modelo;
4 private boolean ligada = false;
5 private int canal = 14;
6 private int volume = 10;
7
8 TV(String modelo) {
9 this.modelo = modelo;
10 this.ligada = false; // a TV, por padrão, está desligada
11 this.canal = 14; // inicia no canal 14
12 this.volume = 10; // inicia com o volume 10
13 }
14
15 String modelo() {
16 return this.modelo;
17 }
18
19 boolean ligada() {
20 return this.ligada;
21 }
22
23 int canal() {
24 return this.canal;
25 }
26
27 int volume() {
28 return this.volume;
29 }
30
31 }
Capítulo 1 – Introdução à Programação Orientada a Objetos 46
https://git.io/
Preste bastante atenção neste código e entenda o seguinte:
• a palavra-chave private antes de cada atributo faz com que eles não sejam acessíveis fora
da classe TV, isto é, uma instrução tv.canal = 12 não compila. Por outro lado, dentro
de TV um this.canal = 12 seria válido (mas não vamos fazer isso, claro).
• o construtor TV(String modelo) define a obrigação de informar pelo menos o modelo
da TV. Ademais, os variáveis de instância recebem seus valores iniciais esperados. O
entendimento é: um new em TV seria como receber a TV com as configurações de fábrica.
• os métodos permitem a consulta dos atributos (que são private e não podem ser lidos ou
alterados). Note que eles não alteram os atributos, eles apenas devolvem o valor deles, isto
é, eles não mudam o estado do objeto. Por este motivo são conhecidos como acessores
ou métodos consulta.
Alerta de Spoilers: ENCAPSULAMENTO!
Encapsulamento é um tópico do próximo capítulo, mas para plantar uma semente, este
estudo de caso já é um exemplo de encapsulamento. Um objeto é encapsulado quando
não é permitido o acesso direto ao seu atributos, que só podem ser consultados ou
alterados através de métodos acessores e de operações.
A nossa segunda versão da TV na classe TV garante alguns requisitos básicos da especificação:
uma TV tem um modelo, pode estar ligada ou não, em um certo canal e com certo volume. Para
validar esse estado inicial segue alguns testes:
Testando o estado inicial da TV
1 class Main {
2 public static void main(String[] args) {
3
4 TV tv = new TV("XingLing T800");
5
6 System.out.println(tv.modelo()); // XingLing T800
7 System.out.println(tv.modelo().equals("XingLing T800"));
8
9 System.out.println(tv.ligada()); // false
10 System.out.println(tv.ligada() == false);
11
12 System.out.println(tv.canal()); // 14
13 System.out.println(tv.canal() == 14);
14
15 System.out.println(tv.volume()); // 10
16 System.out.println(tv.volume() == 10);
17
18 }
19 }
https://git.io/
Capítulo 1 – Introdução à Programação Orientada a Objetos 47
https://git.io/
Passando os testes iniciais agora vem a parte complexa: implementar as operações da TV,
alterar seu estado e, ao mesmo tempo, controlar a complexidade. Primeiro vamos definir como
operações aumentar e baixar o volume, subir e descer o canal e, claro, ligar e desligar a TV.
É importante descrever assim pois essa operações tornar-se-ão métodos no mesmo estilo, por
exemplo aumentarVolume() e descerCanal().
As operações devem respeitar as regras, por exemplo só deve ser possível subir o canal até
o 69, então temos uma decisão de projeto quando a TV já estiver no canal 69 e a operação
subirCanal() for invocada. Já deves ter imaginado uma solução até aqui, vou elencar duas: ou
rejeita a operação (não faz nada) ou roda e vai para o primeiro canal (14).
Decisões de Projeto devem ser tomadas judiciosamente
Tipicamente busca-se uma coerência ou correpondência com o objeto real, por exemplo
o que acontece com uma TV de verdade quando subimos o canal além dos existentes?
Coerência seria não aplicar a mesma regra das operações no canal à operação do
volume, por exemplo voltando para 0 quando o volume passar de 100.
Complexidade: interações entre módulos e operações
A complexidade está no entrelaçamento de módulos e, nesse caso, de operações.
Por exemplo não deve ser possível descer o canal ou aumentar o volume se a TV
estiver desligada. A medida que expandirmos a especificação da TV e incluirmos
mais funcionalidades, aumenta a chance das operações se entrelaçarem, aumentando a
complexidade. Por exemplo, se implementarmos ir para canal e voltar canal anterior,
não é complicado, mas é complexo, estude o cenário: ir para o canal 50, ir para o canal
60, voltar canal anterior, voltar canal anterior, qual o canal agora? Outro cenário: ir
para o canal 50, ir para o canal 60, desligar, ligar, voltar canal anterior, qual o canal
agora? O sintoma visível do aumento da complexidade é quantidade de if’s e else’s e o
aninhamento (um if dentro de outro, dentro de outro, …). Existemmétricas de software
para medir a qualidade dos módulos, como a Complexidade Ciclomática.
Uma das melhores maneiras de especificar e ao mesmo tempo validar é escrever testes que
mostrem a evolução do estado no tempo. A seguir está o Main3 que testa a classe TV:
Testando a evolução do estado da TV
18 // com a TV desligada as operações
19 // não afetam o estado
20 tv.subirCanal();
21 System.out.println(tv.canal());// 14
22 System.out.println(tv.canal() == 14);
23 tv.descerCanal();
24 System.out.println(tv.canal()); // 14
25 System.out.println(tv.canal() == 14);
26 tv.aumentarVolume();
27 System.out.println(tv.volume()); // 10
28 System.out.println(tv.volume() == 10);
https://git.io/
https://pt.wikipedia.org/wiki/Complexidade_ciclom%C3%A1tica
Capítulo 1 – Introdução à Programação Orientada a Objetos 48
29 tv.baixarVolume();
30 System.out.println(tv.volume()); // 10
31 System.out.println(tv.volume() == 10);
32
33 // TV ligada
34 tv.ligarDesligar(); // liga se estiver desligada, desliga se estiver ligada
35 System.out.println(tv.ligada()); // true
36 System.out.println(tv.ligada() == true);
37
38 tv.subirCanal();
39 System.out.println(tv.canal()); // 15
40 System.out.println(tv.canal() == 15);
41
42 tv.aumentarVolume();
43 System.out.println(tv.volume()); // 11
44 System.out.println(tv.volume() == 11);
45 // aumentar 100 vezes o volume
46 for (int i = 0; i 0) {
https://git.io/
Capítulo 1 – Introdução à Programação Orientada a Objetos 49
51 this.volume = this.volume - 1;
52 }
53 }
54
55 void subirCanal() {
56 if (ligada) {
57 if (this.canalAqui mesmo no IFRS usamos Java na
disciplina de POO.
Contudo, pretendo lançar esse livro com outras linguagens de programação. Inclusive, é objetivo
desse livro não se “apegar” a linguagem de programação, sempre tentando abordar os conceitos,
princípios e implementações de forma mais genérica possível, focando sempre no paradigma e
não na linguagem de programação. Dito isso, este livro não é uma referência da linguagem
Java, em vez disso, é uma referência para entender e usar programação orientada a objetos!
Fechando, na verdade, a maioria dos códigos presentes no livro são implementáveis em qualquer
linguagem de programação que ofereça um suporte razoável à POO, com poucos ajustes.
Entenda que POO oferece certas funcionalidades que as linguagens podem oferecer na totalidade,
parcialmente ou até mesmo não oferecer.
Todos os códigos estão disponíveis na on-line em um repositório no github.com acessível
neste endereço: https://github.com/marciojrtorres/poocomhonra. Fique à vontade para baixá-
los, adaptá-los, testá-los, forkar o repositório, bem, faça como quiser, mis códigos son su códigos
(mas use-os por sua conta e risco, hehe :).
Organização dos capítulos
O livro está organizado para cobrir tópicos de POO gradualmente, presumindo que o estudante
já conheça o fundamental de programação, como detalhes de sintaxe, declaração, desvios
condionais, laços, etc (por exemplo, ter passado pela disciplina de Lógica de Programação).
• Capítulo 0 – Programação Modular: revisitar (ou abordar se ainda não foi visto) os
fundamentos de modularização de código por meio de procedimentos codificados como
funções ou métodos estáticos;
• Capítulo 1 – POO com Honra!: oferece uma introdução ao tema central do livro (POO
do jeito certo).
• Capítulo 2 – Implementação de Objetos: …
• Capítulo 3 – Conceitos Fundamentais da POO: …
• Capítulo 4 – Associações entre Objetos: …
• Capítulo 5 – Generalização e Especialização de Objetos: …
• Capítulo 6 – APIs e contratos entre Objetos: …
• Capítulo 7 – Princípios de Projeto de Objetos: …
• Capítulo 8 – O que vem depois?: …
http://www.github.com/
https://github.com/marciojrtorres/poocomhonra
Introdução iv
Para quem é este livro
É importante sempre esclarecer que este é um livro voltado à Educação Profissional, baseado
na minha experiência profissional e no ensino de programação para iniciantes. Ele é útil para
estudantes que já passaram pela disciplina inicial de programação em seus cursos (Lógica de
Programação, por exemplo) e estão aprendendo o paradigma de programação orientado a objetos.
Contudo, essa obra também pode atender programadores formados, entusiastas e autodidatas
de programação que desejam compreender melhor (e melhor aplicar) o paradigma orientado a
objetos.
Profissionalmente, este livro pode ser usado para descobrir melhores estratégias para a modula-
rização de sistemas.
Academicamente, este livro é adequado ao ensino em cursos voltados para a Educação Pro-
fissional, Técnica e Tecnológica, tais como: Técnico em Informática, Técnico em Informática
para Internet, Tecnologia em Análise e Desenvolvimento de Sistemas, Tecnologia em Sistemas
para Internet, além de cursos de formação inicial e continuada na área de desenvolvimento de
sistemas. Mesmo uma disciplina de POO de bacharelados, tais como Ciência/Engenharia da
Computação ou Sistemas de Informação, se procuram um viés pragmático e aplicado, podem
tirar proveito desta obra. Para ajudar, saiba que ele está sendo usado no IFRS para a disciplina
de Programação Orientada a Objetos, cuja ementa está a seguir:
Abstração, classes, instâncias, estado e comportamento, atributos e métodos, co-
mandos e consultas, coesão, encapsulamento e ocultação de informações, associ-
ações, agregação, composição, delegação, dependência e acoplamento, herança e
polimorfismo. Projeto com modelagem visual. Implementação e testes. Noções de
arquitetura e padrões de projeto
Para quem não é este livro
Este livro não cobre construções básicas de programas e não tem o objetivo de ensinar a
programar do zero. O objetivo do livro é ensinar a usar o paradigma orientado a objetos. Se
não sabes programar, não passaste por uma disciplina de lógica de programação ou algoritmos,
então ele pode parecer muito avançado para ti e não te ser muito útil no final.
Não espere deste livro uma abordagem completamente teórica. Ele foi escrito por um praticante
para praticantes.
Não espere, também, uma abordagem extensa da linguagem de programação, neste caso Java. O
objetivo é estudar os conceitos do paradigma orientado a objetos e a linguagem é usada apenas
para materializar esses conceitos.
Se tu já sabes POO e procura um livro que aprofunde esse conhecimento, também pode não ser
uma boa escolha, para estes casos seria importante para ti um referência de Projeto Orientado a
Objetos, como um livro de Padrões de Projeto ou de Arquitetura de Sistemas.
Introdução v
Convenções
A seguir algumas convenções a respeito da abordagem, tipografia e layout.
Acrônimos
Algumas palavras e nomes aparecem abreviados, usando siglas ou acrônimos. Na primeira vez
que forem exibidos constará o nome completo e no restante do livro (salvo exceções) é usado
o acrônimo. Por exemplo, Programação Orientada a Objetos será abreviada como “POO”, assim
como Orientação a Objetos como “OO”.
Inglês
Este livro tem a proposta de proporcionar material técnico no nosso idioma. Entretanto, na
área de desenvolvimento de softwares, o idioma inglês é predominante – frequentemente ele
é inevitável. Por este motivo, termos e nomes amplamente conhecidos em inglês terão uma
explicação em português, mas também serão apresentados na sua forma original (em inglês).
É importante te habituares, pois mesmo que pareça estranho alguém dizer “dropa a tabela”,
é o modo como as pessoas falam no ambiente de trabalho, esse “dialeto”, por assim dizer, é
importante sobretudo em um livro que prepara para o mundo do trabalho.
Códigos
Trechos de código são exibidos usando fonte de largura fixa. Em meio ao texto eles aparecem
como “… usamos o método isFuture(Date):boolean para …”, semelhante a notação UML. As
assinaturas demétodos são apresentadas como Classe.metodo(TipoArgumento):TipoRetorno.
Trechos mais extensos de código são apresentados em bloco, com título, linhas numeradas
(quando for necessário) e link para obter o fonte. Por exemplo:
Exemplo de um código de exemplo
1 /*
2 * Os códigos aparecerão em fonte de largura fixa,
3 * como neste bloco.
4 */
5 public class UmaClasse {
6 private String umAtributo;
7 public String umMetodo(int umParametro) {
8 int umRetorno = umParametro * 2;
9 return this.umAtributo + umRetorno;
10 }
11 // ...
12 }
https://git.io/vKyJK
Obs.: comentários seguido por reticências // ... significam código omitido, pois às vezes o
código inteiro é muito longo para ser colocado na listagem, então a parte irrelevante para o
exemplo é omitida. Se quiseres ver o código inteiro visite o link no fim da listagem. Minha
sugestão é que copies e faças experiências com os códigos.
https://git.io/vKyJK
Introdução vi
Dicas, avisos, observações e milongas
O livro está cheio de dicas, a maioria é relacionada com a honra da programação orientada a
objetos, isto é, quando ele está sendo aplicado corretamente, ou não.
Tu estás honrando o paradigma quando …
Considere fazer o que é dito nessas caixas.
Shame, shame, shame, …
Se não entendeu a referência, o que é dito nessas caixas deve ser evitado, pode ser uma
desgraça para ti e uma decepção para tua família.
WTF!
Caixas como essa apresentam os problemas escondidos nos detalhes.
Observações gerais …
Nem para o bem, nem para o mal, só um detalhe técnico, geralmente especificidades
da implementação na linguagem de programação.
Exercícios: essenciais para o aprendizado
Caixas como essa enunciam exercícios e/ou desafios.
Milongas e devaneios.
Essas caixas servem para abrigar devaneios de um velho programador milongueiro. Não
contém, a rigor, conteúdo técnico, então podes ignorá-las sem problemas.
Sobre o Autor
Atualmentesou Professor no IFRS e atuo nos cursos de Analise e Desenvolvimento de Sistemas
e Técnico em Informática para Internet.
Meus alunos (os engraçadinhos) me perguntam: “professor, tu trabalha ou só dá aula?” (sic)
É a vida de quem ensina. Eu trabalho em sala de aula hoje, mas ainda participo de projetos
internos e dou uns pitacos em sistemas alheios (atividade conhecida como consultoria :).
Introdução vii
Atrás disso, tenho uma história longa, passei por vários marcos na linha do tempo da evolução
da computação. Para me conheceres melhor vou contar um pouco dessa história no devaneio a
seguir:
“Eu nasci a dez mil anos atrás”. Comecei programando em Basic, num CP500 da
Prológica. Sem Internet, se aprendia lendo revistas técnicas, transcrevendo códigos
e fazendo experiências. Mais tarde comecei a desenvolver aplicações comerciais
com dBase e então Clipper, ambos sobre a plataforma MS-DOS. Joguei Prince of
Persia, Wolfenstein e o primeiro DOOM - tinha que usar o DOS/4GW para liberar a
memória estendida. Jámontei meu próprio computador – quando se selecionava IRQ
por jumpers/switches. Vivenciei a ascensão da interface gráfica - não aguentavamais
ver caracteres em fósforo verde. Instalei o Windows 95 - malditos 13 disquetes. Tive
umKitMultimídia da Creative – e uma placa de vídeo Voodoo.Migrei meus sistemas
de Clipper para Visual Basic e mais tarde Delphi. Usei a Internet quando ainda só
existia HTML com “meia dúzia” de tags – nada de CSS ou JS. Acompanhei a ascensão
da Internet e da Web. Presenciei o início do Linux, sua evolução e importância para
a consolidação dos sistemas on-line – junto com Perl, Apache, MySQL, PHP, etc. Já
instalei o Conectiva Linux, compilei o Kernel e aprendi a usar uma linha de comando
de verdade. Comecei a programar em Java a partir da versão 1.3 – ainda sem enums,
generics, autoboxing etc – e foi meu primero contato com Orientação a Objetos –
velhos hábitos procedimentais são difíceis de perder. Observei a Googlificação –mas
usei o Cadê e o AltaVista. Acompanhei o crescimento do Comércio Eletrônico - e
também o estouro da bolha da Internet.
Hoje, ainda tenho dúvidas se sou um programador que ensina ou um professor que programa.
Conheci tecnologias e empresas, mas o que levo comigo são as pessoas, colegas, amigos e alunos,
foi e é um privilégio trabalhar com tanta gente agradável e talentosa.
Capítulo 0 – Programação Modular
Pequeno…
O mundo é muito grande, mãe. (jovem Clark Kent)
Então faça-o ficar pequeno.
– Martha Kent
Existem muitas definições para o que é Programação Modular, mas vamos lidar com a que está
disponível no wiki do Cunningham & Cunningham:
Programação Modular é o ato de projetar e escrever programas como interações entre
funções onde cada uma realiza uma única e clara funcionalidade, e que tem omínimo
de efeitos colaterais entre elas.
Este capítulo traz uma pequena introdução à modularização para reaproveitamento de código,
começando com um abordagem Procedimental (separando as funcionalidades em funções/pro-
cedimentos) e então comparando (ainda sem muitos detalhes) com a abordagem Orientada a
Objetos (separando funcionalidades em objetos e métodos).
Ao final deste capítulo tu deves saber ..
• o que se entende por módulo em programação;
• um pouco sobre o paradigma de Programação Procedimental;
• o que são procedimentos e como implementá-los usando funções (ou métodos);
• os príncipios básicos de testagem e como escrever pequenos casos de teste;
• como compor módulos e procedimentos para implementar funcionalidades mais
complexas;
• quais são as estratégias comuns para organizar dados, informações e algoritmos;
• usar o básico de vetores (arrays);
• como a Programação Orientada a Objetos (POO) organiza a modularização;
• como a POO se diferencia da abordagem Procedimental;
Modularização
A modularização dos códigos é um dos principais recursos para construir softwares com alto
nível de qualidade. Em poucos palavras, a meta é simples: extrair uma lógica repetida (código
redundante) e compartilhar através de partes reaproveitáveis (módulos).
http://c2.com/cgi/wiki?ModularProgramming
Capítulo 0 – Programação Modular 2
Programador, reconheça teu pior inimigo: CTRL + C CTRL + V!
Todo trecho de código copiado e colado, isto é, que precisa ser usado em várias partes
do programa, é um candidato a virar ummódulo, seja através de uma função ou objeto.
Copiar e colar, embora útil no primeiro instante, traz problemas terríveis a longo prazo,
como a necessidade de corrigir o mesmo bug espalhado em todo o código.
Nos primórdios da programação a modularização era realizada através de Rotinas e mais tarde
por Procedimentos, bem antes de então chegarmos no paradigma Orientado a Objetos (OO).
Materializando isso, geralmente os módulos são separados em diversos arquivos, que contém
as rotinas/procedimentos/funções/classes/métodos. Entenda que, antes de tudo, é perfeitamente
possível escrever um programa inteiro em um único arquivo e até em uma única listagem
“corrida” de código (sem separações). Entenda, também, que a granularidade pode ser variável,
isto é, o mesmo programa pode ser separado em dezenas ou centenas de arquivos, cada um com
uma, duas ou até dúzias de rotinas/procedimentos/funções/classes/métodos.
Embora modularizar traga um trabalho adicional de projetar (pensar) na separação, ainda assim
os benefícios são incontestáveis, pagando todo o esforço inicial extra, valendo o trade-off ². Entre
os benefícios alguns mais importantes são:
• Com módulos há menos código em cada arquivo, resultando em códigos mais simples e
compreensíveis;
• As lógicas são reutilizáveis, evitando a rescrita do mesmo código várias vezes;
• Os membros da equipe podem trabalhar em módulos diferentes;
• Existe uma maior facilidade para identificar e corrigir erros, dado o isolamento, quando
eles estão contidos em módulos;
• O mesmo módulo pode ser reaproveitado em vários softwares;
• Cada módulo pode ser testado separadamente.
Os benefícios citados são, entre outros, os principais responsáveis por uma gestão básica da
qualidade do software. No tópico a seguir vem o primeiro exemplo de como construir partes
reusáveis de códigos através de procedimentos (funções).
Modularização na Programação Procedimental
A separação da lógica repetida/reusável em procedimentos é a base da programação procedi-
mental. A implementação desses procedimentos depende muito da linguagem de programação
utilizada, por exemplo, são usadas as keywords Procedure em Pascal, Sub e Function em
Visual Basic. A seguir um procedimento em Pascal para (exemplo “bobo”) mostrar a soma de
dois números:
²a relação de custo/benefício é conhecida como o trade-off, onde uma escolha é pesada segundo seus prós e contras em
uma balança.
https://pt.wikibooks.org/wiki/Pascal/Procedures
https://msdn.microsoft.com/en-us/library/y6yz79c3.aspx
Capítulo 0 – Programação Modular 3
Procedimento para somar dois números em Pascal
Procedure Soma (Var Valor_1, Valor_2 : Integer);
Var Soma : Integer;
Begin
Soma := Valor_1 + Valor_2;
Writeln ('Soma = ', Soma);
End;
// https://gist.github.com/marciojrtorres/3065c164af0b89eb033a108b1936ed3a
Nas linguagens modernas os procedimentos são implementados como funções (ou métodos).
Existem diversas sintaxes³ para declarar uma função, dependendo da linguagem de programação,
por exemplo: def em Python e Ruby, func em Google GO, fun em Kotlin e, mais óbvio,
function em JavaScript e PHP.
Na linguagem Java a definição de funções/procedimentos é feita com métodos estáticos (não é
a única, em C# também por exemplo). A presença de métodos estáticos em códigos escritos na
linguagem Java caracteriza funcionalidades Procedurais em meio à Orientação a Objetos.
Procedimentos declarados como funções não devem ser con-
fundidos com o paradigma de Programação Funcional
Funções, em C/C++, Python, JavaScript, Perl, etc, ou métodos estáticos, em Ja-
va/C#/Swift, não devem, nunca, ser confundidos com Programação Funcional, para-
digma onde a noção de “módulo” está para a codificação de algoritmoscomputacionais
tais como funções matemáticas e, geralmente, baseia-se em estados imutáves (mutabili-
dade será visto adiante), como em Haskell, Lisp, F#, etc. Não cometa essa gafe, hashtag
#pelamordeDeus.
Os procedimentos são projetados para executar uma lógica, recebendo ou não uma entrada e
devolvendo ou não um valor, isto é, tanto a entrada de dados como a saída de dados é opcional.
Para exemplificar, considere um programa simples, que lê dois números e imprime o MMC. A
seguir uma implementação inicial que não faz uso de procedimentos:
Calculando um MMC sem um procedimento
1 import java.util.Scanner; // scanner é um módulo!
2 public class Proc1 {
3 public static void main(String[] args) {
4 // ler dois números
5 Scanner scan = new Scanner(System.in);
6 int numero1 = scan.nextInt();
7 int numero2 = scan.nextInt();
8 // descobrir o maior número
9 int maior = numero1 > numero2 ? numero1 : numero2;
10 int mmc = maior;
11 // somar o maior enquanto não for divisível por ambos
12 while (mmc % numero1 != 0 || mmc % numero2 != 0) {
13 mmc += maior;
³regras da linguagem de programação para definir o conjunto de palavras (símbolos) permitidos e suas combinações.
https://docs.python.org/3/tutorial/controlflow.html#defining-functions
https://www.ruby-lang.org/en/documentation/quickstart/2/
https://tour.golang.org/welcome/1
https://kotlinlang.org/docs/reference/functions.html
https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Functions
http://php.net/manual/pt_BR/language.functions.php
Capítulo 0 – Programação Modular 4
14 }
15 // imprimir mmc
16 System.out.println(mmc);
17 }
18 }
https://git.io/vKyfh
A implementação sem separar a lógica em um procedimento incorre na duplicação do código
em operações subsequentes, digamos que o cálculo do MMC seja necessário em outras partes do
programa, o código entre as linhas 8 e 16, pelo menos, teriam que ser reintroduzidos. Considere
o mesmo exemplo com duas operações:
Calculando dois MMC’s sem um procedimento
1 import java.util.Scanner; // scanner é um módulo!
2 public class Proc2 {
3 public static void main(String[] args) {
4 // ler dois números
5 Scanner scan = new Scanner(System.in);
6 int numero1 = scan.nextInt();
7 int numero2 = scan.nextInt();
8 // descobrir o maior número
9 int maior = numero1 > numero2 ? numero1 : numero2;
10 int mmc = maior;
11 // somar o maior enquanto não for divisível
12 while (mmc % numero1 != 0 || mmc % numero2 != 0) {
13 mmc += maior;
14 }
15 // imprimir mmc
16 System.out.println(mmc);
17
18 // ler mais dois números
19 int numero3 = scan.nextInt();
20 int numero4 = scan.nextInt();
21 // descobrir o maior número
22 maior = numero3 > numero4 ? numero3 : numero4;
23 mmc = maior;
24 // somar o maior enquanto não for divisível
25 while (mmc % numero3 != 0 || mmc % numero4 != 0) {
26 mmc += maior;
27 }
28 // imprimir mmc
29 System.out.println(mmc);
30 }
31 }
https://git.io/vKyJq
A extração do procedimento deve ser identificada através dos dados de entrada e lógica
necessária, observando o que há de comum nos dois cálculos subsequentes: leitura de dois
números, descoberta do maior, loop e print. Somente a entrada é diferente, levando a escrever o
seguinte procedimento entre as linhas 7 e 18 do código a seguir:
https://git.io/vKyfh
https://git.io/vKyJq
Capítulo 0 – Programação Modular 5
Um procedimento para calcular o MMC
1 import java.util.Scanner; // scanner é um módulo!
2 public class Proc3 {
3 // static = método estático (representa uma função/procedimento em Java)
4 // void = sem retorno (return)
5 // mmc = nome da função
6 // int n1, int n2 = dados de entrada necessários
7 static void mmc(int n1, int n2) {
8 // seja qual for a variável elas entram como n1 e n2
9 // descobrir o maior número
10 int maior = n1 > n2 ? n1 : n2;
11 int mmc = maior;
12 // somar o maior enquanto não for divisível
13 while (mmc % n1 != 0 || mmc % n1 != 0) {
14 mmc += maior;
15 }
16 // imprimir mmc
17 System.out.println(mmc);
18 }
19
20 public static void main(String[] args) {
21 // ler dois números
22 Scanner scan = new Scanner(System.in);
23 int numero1 = scan.nextInt();
24 int numero2 = scan.nextInt();
25 // chamada/invocação da função/procedimento
26 mmc(numero1, numero2);
27 // ler mais dois números
28 int numero3 = scan.nextInt();
29 int numero4 = scan.nextInt();
30 // chamada/invocação da função/procedimento
31 mmc(numero3, numero4);
32 }
33 }
https://git.io/vKyJZ
Na linha 7 está a assinatura do método, que neste exemplo representa um procedimento. A
assinatura, em poucas palavras, descreve o nome do procedimento, entrada e retorno, podendo
ser descrita como Proc3.mmc(int, int):void, que significa um procedimento com o nome de
mmc, pertencente aomódulo Proc3 (em Java ummódulo é uma classe) que recebe dois parâmetros
inteiros int, int e não possui retorno void (void pode ser traduzido livremente como vazio).
O procedimento anterior funciona bem, tu mesmo podes fazer os testes. Mas, como se testa? A
maneira trivial de testar é chamar o procedimento comMMCs conhecidos e observar se a reposta
é esperada, por exemplo, digitando 3 e 5 o resultado deve ser, inevitavelmente, 15. Realizar testes
manuais, entrando com os valores e observando as saidas, mesmo com valores triviais, também
é a testagem mais simplificada possível.
https://git.io/vKyJZ
Capítulo 0 – Programação Modular 6
Não é objetivo deste livro capacitar em Testagem de Software
Entretanto, para tornar-se um bom programador é essencial saber testar corretamente
e usar estratégias para escrever código eficiente e correto. A qualidade do software
percebida pelos usuários está ligada diretamente a quantidade de erros percebíveis
(sim, aquelas caixas de erro). Ainda, o pior caso é quando o software oferece respostas
incorretas, sem quebrar.
Testagem
Sem ir muito ao fundo do assunto, a testagem adequada é realizada com a introdução de
um conjunto de entradas variadas, válidas e inválidas, as quais tenham saídas previsíveis
(esperadas). A previsibilidade é o ponto-chave da testabilidade e para isso os procedimentos
devem fornecer alguma saída, seja a resposta correta ou um tratamento de erro. O procedimento
anterior não oferece uma saída, não tem retorno (void), o println faz parte da lógica do
procedimento. Os dois problemas dessa abordagem são:
• a estratégia de impressão do resultado é “fixa”,
• o procedimento não oferece um retorno testável.
Por esses motivos o procedimento será alterado para oferecer um retorno, conforme exemplo a
seguir:
Um procedimento com retorno
1 import java.util.Scanner;
2
3 public class Proc4 {
4 // esse int antes do nome signigica um retorno inteiro
5 static int mmc(int n1, int n2) {
6 int maior = n1 > n2 ? n1 : n2;
7 int mmc = maior;
8 while (mmc % n1 != 0 || mmc % n1 != 0) {
9 mmc += maior;
10 }
11 // aqui trocamos o print pelo return
12 return mmc;
13 }
14
15 public static void main(String[] args) {
16 Scanner scan = new Scanner(System.in);
17 int numero1 = scan.nextInt();
18 int numero2 = scan.nextInt();
19 // agora a chamada traz um retorno
20 // que pode ser atribuído e impresso
21 int resultado = mmc(numero1, numero2);
22 System.out.println(resultado);
23 // ou então ser feito diretamente:
24 System.out.println(mmc(scan.nextInt(), scan.nextInt()));
25 }
26 }
Capítulo 0 – Programação Modular 7
https://git.io/vKyJW
Na linha 5, o int antes do nome do procedimento declara um retorno. Isto quer dizer que o
procedimento devolve um inteiro que pode ser usado para qualquer fim, como ser impresso na
linha 22. É importante notar as mudanças nas responsabilidades: antes a responsabilidade do
println era do procedimento e agora é da seção principal main.
O próximo passo, agora, é escrever os testes. Preparar a testagem não é complicado, exige apenas
duas medidas:
• eliminar a entrada do usuário e introduzir constantes literais (ex.: int numero1 = 3),
• escrever assertivas simples (expressões booleanas) para declarar o resultado esperado.
Um true impresso significa que o teste passou e um false (ou exceção) significa que o testefalhou (ou uma situação inesperada foi encontrada, um bug). Confira a testagemdo procedimento
mmc no código a seguir:
Testando o procedimento com constantes literais e assertivas
1 import java.util.Scanner;
2 public class Proc5 {
3
4 static int mmc(int n1, int n2) {
5 int maior = n1 > n2 ? n1 : n2;
6 int mmc = maior;
7 while (mmc % n1 != 0 || mmc % n1 != 0) {
8 mmc += maior;
9 }
10 return mmc;
11 }
12
13 public static void main(String[] args) {
14 int numero1 = 3;
15 int numero2 = 5;
16 int resultado = mmc(numero1, numero2);
17 // esperado que o mmc entre 3 e 5 seja 15
18 // deve imprimir `true`
19 System.out.println(resultado == 15);
20 // fazendo testes diretamente:
21 System.out.println(mmc(5, 6) == 30);
22 // testando no limite:
23 System.out.println(mmc(14223, 77323) == 1099765029);
24 }
25 }
https://git.io/vKyJ4
A testagem é considerada bem sucedida quando revela falhas
ou erros.
Se nunca tiveste contato com atividades de testagem, essa última frase deve ter te
causado um mind blown, pois se eu escrevo um código não devo esperar que ele
funcione? É por esse motivo que, na “vida real”, os testes são realizados por outras
pessoas, em alguns casos, por outras empresas.
https://git.io/vKyJW
https://git.io/vKyJ4
Capítulo 0 – Programação Modular 8
As expressões booleanas nas linhas 19, 21 e 23 representam três testes: dois óbvios, que podem
ser obtidos de cálculo mental, e um extrapolado (no limite, o mais importante!). Testar os limites
é importante porque é “nas bordas” que falhas e bugs são revelados e, grave isso na memória: o
objetivo de testar é provar que o programa não funciona. Neste exemplo, na verdade, foram
realizados poucos testes, além de testar situações limites é importante adicionar mais testes afim
de encontrar falhas e corrigí-las, até todos os testes passarem. Para minha felicidade (ou não,
lembre :P) todos os testes passaram:
Testes passando
javac Proc5.java; java Proc5
true
true
true
A linguagem Java permite escrever assertivas de verdade com a
instrução assert
Nem todas as linguagens possuem uma sintaxe especial para escrever asserti-
vas. Optei por expressões booleanas por serem simples de escrever em qual-
quer linguagem, inclusive é um propósito lançar esse livro com implementação
em outras linguagens como Python, Ruby, C# e PHP. Entretanto, a linguagem
Java disponibiliza uma instrução especial para declarar assertivas, por exemplo,
a instrução System.out.println(mmc(5, 6) == 30) poderia ser escrita como
assert(mmc(5, 6) == 30): "mmc(5, 6) == 30". Há um porém: para executar
essas assertivas a classe principal deve ser executada com o argumento -ea (Enable
Assertions) como java -ea Proc5. Se for da tua preferência realizar uma testagem
mais sofisticada experimente substituir os prints com expressões booleanas por asser-
tivas reais com assert.
Go Pro!
Se queres te tornar um Testador de Software, profissional especialista em testagem e
membro da equipe de Qualidade de Software, considere ler a respeito de Testagem,
Teste Unitário (JUnit em Java), Stubs, Mocks, Test-Driven Development etc.
Programadores honrados procuram um Oráculo
As situações de teste mais extremas não são selecionadas “da cabeça”, obviamente,
então é preciso encontrar uma fonte de informação irrefutável que valide a assertiva.
Por exemplo, oMMC de 14223 e 77323 é 1099765029, meu Oráculo: WolframAlpha⁴!
Experimente: https://www.wolframalpha.com/input/?i=lcm+14223+77323.
⁴o WolframAlpha é excelente para testar procedimentos (e depois objetos/métodos) que envolvam matemática!
https://www.wolframalpha.com/input/?i=lcm+14223+77323
Capítulo 0 – Programação Modular 9
Especificação
Procedimentos e, posteriormente, objetos são projetados e implementados a partir de especifi-
cações. As especificações devem ser corretas, precisas, claras e válidas! Sem uma especificação
válida é impossível assegurar que as respostas são corretas ou mesmo provar que não são. Então,
vamos obter a especificação do MMC:
Em aritmética e em teoria dos números o mínimo múltiplo comum (mmc) de dois
inteiros a e b é o menor inteiro positivo que é múltiplo simultaneamente de a e de b.
Se não existir tal inteiro positivo, por exemplo, se a =0 ou b = 0, então mmc(a, b) é
zero por definição.
– Wikipédia em https://pt.wikipedia.org/wiki/M%C3%ADnimo_m%C3%BAltiplo_
comum
Partindo dessa especificação, podemos assegurar que o procedimento implementado está em
conformidade com ela? Vamos tentar provar que não, explorando as situações excepcionais e
escrevendo os testes a seguir contra a especificação:
Testando contra a especificação
// menor inteiro positivo que é múltiplo
// simultaneamente de a e de b
System.out.println(mmc(5, -6) == 30);
System.out.println(mmc(-5, 6) == 30);
System.out.println(mmc(-5, -6) == 30);
// se a = 0 ou b = 0, então mmc(a, b)
// é zero por definição
System.out.println(mmc(0, 10) == 0);
System.out.println(mmc(0, -10) == 0);
System.out.println(mmc(10, 0) == 0);
System.out.println(mmc(-10, 0) == 0);
System.out.println(mmc(0, 0) == 0);
https://git.io/vKyJu
Não trate como decepcionante, mas como esclarecedor. Dos três primeiros testes, com nú-
meros negativos, o primeiro e terceiro falham. A seção de testes com zero causa um erro
logo no primeiro Exception in thread "main" java.lang.ArithmeticException: /
by zero. Obter false indica uma falha e quer dizer que não está conforme a especificação
pois retorna uma resposta incorreta (as duas falhas retornaram 5 e -5 respectivamente). Uma
exceção, ou “quebra” do programa, indica um erro. Resumindo, o procedimento não cumpre a
especificação, isto foi provado (e provar que não funciona é, acredite, bom!).
Procedimentos que passam em todos os testes não são livres
de falhas!
A testagem assegura que, nos casos de teste pontuais, o procedimento funciona
corretamente, contudo é impossível cobrir todas as situações possíveis (conhecido
como teste exaustivo), por investimento de tempo (e dinheiro) é importante selecionar
criteriosamente os casos de teste que, claro, têm maior probabilidade de revelar um
defeito.
https://pt.wikipedia.org/wiki/M%C3%ADnimo_m%C3%BAltiplo_comum
https://pt.wikipedia.org/wiki/M%C3%ADnimo_m%C3%BAltiplo_comum
https://git.io/vKyJu
Capítulo 0 – Programação Modular 10
Exercício: bora corrigir o procedimento meu velho!
Corrija o procedimento de mmc apresentado anteriormente para adequá-lo à especifi-
cação e passar em todos os testes de condições excepcionais.
Existem frameworks específicos para testes unitários
Se quiseres te aprofundar ou até procurar uma formação de Testador de Software,
considere estudar testes unitários. Em Java o framework mais popular é o JUnit, que
possui variantes para várias linguagens/plataformas (PHPUnit, RUnit, NUnit, etc).
Exercício: potência
Projete e implemente um procedimento para calcular potências. Analise quais são as
entradas necessárias e o que é retornado. Escreva testes conta a especificação. Lembre
de testar situações limites e casos excepcionais.
Modularização na Programação Orientada a Objetos
A POO se difere da programação procedimental quando combina os dados e o algoritmo em
uma única unidade (módulo). Na POO a lógica é acessada a partir dos dados. Ainda temos o
livro inteiro para discutir isso, mas apenas para ilustrar considere um exemplo “bobo”: calcular
o dobro de um número inteiro. Na programação procedimental basta criar um procedimento
dobro(int):int, ele recebe um inteiro e devolve outro, não é complicado, veja a seguir:
Procedimento para obter o dobro de um número
public class Proc7 {
static int dobro(int n) {
return n + n; // ou n * 2
}
public static void main(String[] args) {
System.out.println(dobro(0) == 0);
System.out.println(dobro(5) == 10);
System.out.println(dobro(90) == 180);
}
}
https://git.io/vKyJ2
No procedimento o número é um parâmetro do procedimento. Na POO não há procedimento,
há uma classe e por consequência um objeto que retém a informação (o número) e a lógica (o
que seria um “procedimento” ou, no dialeto OO, o método!). A seguir a versão OO do dobrode
um número:
https://git.io/vKyJ2
Capítulo 0 – Programação Modular 11
Objeto para obter o dobro de um número
class Numero {
int n;
Numero(int n) {
this.n = n;
}
int dobro() {
return this.n + this.n; // ou this.n * 2
}
public static void main(String[] args) {
Numero n1 = new Numero(5);
System.out.println(n1.dobro() == 10);
System.out.println(new Numero(90).dobro() == 180);
Numero zero = new Numero(0);
System.out.println(zero.dobro() == 0);
}
}
https://git.io/vKyJa
Em Java a Programação Procedimental é implementada com
static
Procedimentos são suportados na linguagem Java através de métodos estáticos. A
presença de static, além do main(String[] args), revela um estilo Procedimental
de programação em vez de Orientado a Objetos.
Puristas de OO repudiam veementemente o contexto static
Nunca defenda métodos estáticos em uma roda de programadores defensores do
paradigma Orientado a Objetos, a não ser que queira ser expulso, excomungado,
deserdado, enfim, cair em desonra.
Se nunca viste POO antes, o código anterior pode parecer intimidador, confuso e até exagerado,
numa avaliação mais crítica, pois POO geralmente exige mais código que o Procedimental
para declarar as estruturas básicas. A POO oferece uma nova visão e interpretação (por isso
paradigma) de como implementar uma especificação. Direto e em poucas palavras, no código
anterior a noção de Número foi implementada com uma classe class Numero, da qual foram
obtidas instâncias através de um construtor new Numero(5), que inicializa um atributo int n,
até ser invocado o método dobro(), que então retorna o dobro do valor inicializado. O MMC
de dois números também pode ser portado para POO, no mesmo sentido: cria-se uma classe
que represente números, instancia e inicializa o objeto com 2 números e então invoca o método
mmc(). Não será explicado agora, mas considere um desafio, se conseguires realizá-lo então estás
a meio caminho andado.
https://git.io/vKyJa
Capítulo 0 – Programação Modular 12
Desafio: tu consegues implementar oMMC orientadoaobjetos?
Seguindo a mecânica apresentada no exemplo anterior é possível identificar um padrão
e implementar o MMC. Procure entender o que é a entrada, lógica e saída.
OO, ser ou não ser …
Nem todas as linguagens suportam os conceitos e princípios da OO na totalidade. Contudo, nas
linguagens multi-paradigma e concordantes com OO (Ruby por exemplo) é possível perceber
claramente quando um código é OO ou Procedimental. Por exemplo: print(2.dobro()) é
OO e print(dobro(2)) é procedimental. O número 2 é a informação base, tente entender,
comparando os dois exemplos, como a funcionalidade dobro é executada, ou ela é realizada
partir do 2 ou para o 2.
Subprocedimentos
Procedimentos isolados não servem para construir um programa completo, normalmente os
procedimentos (e vale omesmo depois para os objetos) são compostos para resolver determinados
problemas, buscando o reuso de algoritmos comuns e separação de responsabilidades em
procedimentos menores.
Considere de volta o MMC e agora também o MDC. O MMC pode ser calculado a partir do
MDC com a seguinte expressão mmc(a, b) = a / mdc(a, b) * b. No final podemos fazer o
MMC composto pelo subprocedimento mdc(int,int):int, que abre outra oportunidade para
um subprocedimento de seleção do menor número menor(int,int):int. A seguir o código
que demonstra essa utilização de subprocedimentos:
Procedimentos que “chamam” outros procedimentos
public class Proc8 {
static int menor(int a, int b) {
System.out.println("calculando o menor");
int menor = aaumento da indireção.
É um desafio para o programador manter as dependências sob controle e isto é
feito, principalmente, com testagem. Semelhante a um processo de engenharia com
componentes físicos, cada parte é testada individualmente e depois coletivamente na
interação com outras peças. Assegurar a qualidade das partes é essencial para ter
um todo de qualidade!
Para estudo eu implementei os procedimentos e subprocedimentos, enfim, todas as funções
estão no código que vem a seguir. Não é a melhor implementação possível, há um problema de
imprecisão, da própria implementação como da essência do ponto flutuante, mas é boa suficiente
para estudo.
Combinando procedimentos
public class Proc9 {
static double potencia(double base, int expoente) {
double potencia = base;
for (int i = 1; i" a "
+ dataFim[0] + "/"
+ dataFim[1] + "/"
+ dataFim[2]);
}
}
https://git.io/vK9e2
zero based indexes e one based indexes
Na maioria das linguagens os índices dos vetores começam em 0 (zero based). Algumas
outras linguagens são one based como Julia, MatLab, Lua, por exemplo. Mal intepretar
o índice é uma das principais causas de bugs em aplicativos, ao mesmo tempo, um dos
erros mais frequentes cometido pelos iniciantes.
Conter os dados em vetores (e depois em classes/objetos) facilita o trânsito das informações,
permitindo que os dados andem juntos. Para demonstrar vamos evoluir o exemplo anterior, ex-
traindo a apresentação da data “d/m/a” para um procedimento formataData(int[]):String
como pode ser visto no código a seguir:
https://git.io/vK9e2
Capítulo 0 – Programação Modular 20
Estruturas de dados passando por funções
import java.util.Scanner;
public class Estrutura3 {
static String formataData(int[] data) {
return data[0] + "/"
+ data[1] + "/"
+ data[2];
}
public static void main(String[] args) {
int[] dataInicio = new int[3];
int[] dataFim = new int[3];
dataInicio[0] = scan.nextInt();
dataInicio[1] = scan.nextInt();
dataInicio[2] = scan.nextInt();
dataFim[0] = scan.nextInt();
dataFim[1] = scan.nextInt();
dataFim[2] = scan.nextInt();
System.out.println(formataData(dataInicio) + " a "
+ formataData(dataFim));
}
}
https://git.io/vK9ew
Se os dados não fossem contidos a função teria a assinatura formataData(int,int,int):String,
recebendo os três int’s (dia, mês e ano) e devolvendo a String com a data formatada. O
procedimento parece bom suficiente, mas falta um detalhe importante que é indispensável: os
testes.
Os testes defendem a honra dos programadores
Pelos testes os programadores asseguram a qualidade de seu trabalho. Eles não são
garantia do código ser 100% livre de bugs, mas representam o zelo do profissional com o
código e que a maioria dos bugs foram encontrados reduzindo, e muito, a probabilidade
de falhas.
https://git.io/vK9ew
Capítulo 0 – Programação Modular 21
Testando estruturas e procedimento
1 public class Estrutura4 {
2
3 static String formataData(int[] data) {
4 return data[0] + "/"
5 + data[1] + "/"
6 + data[2];
7 }
8
9 public static void main(String[] args) {
10 int[] dataInicio = { 1, 7, 2016};
11 int[] dataFim = {31, 7, 2016};
12
13 System.out.println(
14 formataData(dataInicio).equals("1/7/2016"));
15 System.out.println(
16 formataData(dataFim).equals("31/7/2016"));
17 System.out.println(
18 formataData(new int[]{10,10,2010}).equals("10/10/2010"));
19 }
20 }
https://git.io/vK9er
Este último código de exemplo, além de testar o nosso procedimento “bobo” formataData,
apresenta detalhes básicos importantes para lidar com vetores. Nas linhas 10 e 11 os vetores
são declarados e inicializados com os valores inteiros literais. Na linha 18 o vetor é criado e
inicializado ao mesmo tempo que é passado como argumento para o procedimento.
Algoritmo + Estrutura de Dados = Programa
Até aqui, na evolução desse exemplo, o que deve ficar claro é a separação do algoritmo
(procedimento ou função) e da estrutura de dados (vetor). Essa característica é uma
da principais, se não for a principal, diferença entre o paradigma Procedimental e
o Orientado a Objetos, sendo que na POO os dados e a lógica convivem na mesma
unidade: a classe.
Agora vamos a um exemplo mais elaborado. Além disso, para avançar um pouco mais na
programação modular, vamos separar o corpo principal do programa (o main) da unidade com
os procedimentos: Data.formataData e Data.somaDias(int[], int):int[] dentro de um
módulo Data (class Data em Java).
Neste estudo de caso, vamos projetar um procedimento para adicionar dias a uma data. O
procedimento recebe um vetor com a data e a quantidade de dias em inteiro e então devolve
outro vetor com o resultado, segundo essa assinatura: somaDias(int[], int):int[]. Não é
uma lógica simples ou trivial, trabalhar com datas é sempre desafiador, pois as regras não têm
um padrão simples: alguns meses têm 30 dias, outros têm 31, um tem 28 ou 29 nos anos bissextos.
Entretanto, o programador pode usar a estratégia de reduzir o problema em parte menores e
mais simples de serem resolvidas. Essa estratégia é conhecida como Computational Thinking
https://git.io/vK9er
http://lite.acad.univali.br/pensamento-computacional/
Capítulo 0 – Programação Modular 22
(Pensamento Computacional) e consiste em analisar uma instância do problema, reconhecer um
padrão, decompor o problema, analisar solucionar e solucionar as partes até projetar uma solução
genérica para o problema inteiro. Começando com um esboço:
Esboço dos módulos Data e DataMain
// arquivo Data.java
public class Data {
static int[] somaDias(int[] data, int dias) {
int[] resultado = new int[3];
// aqui está o problema!!!
return resultado;
}
static String formataData(int[] data) {
return data[0] + "/"
+ data[1] + "/"
+ data[2];
}
}
// arquivo DataMain.java
public class DataMain {
public static void main(String[] args) {
int[] dataInicio = { 1, 7, 2016};
int[] dataFim = {31, 7, 2016};
System.out.println(
Data.formataData(dataInicio).equals("1/7/2016"));
System.out.println(
Data.formataData(dataFim).equals("31/7/2016"));
System.out.println(
Data.formataData(new int[]{10,10,2010}).equals("10/10/2010"));
int[] umaData = {1, 1, 2016};
int[] resultado = Data.somaDias(umaData, 40);
System.out.println(resultado[0] == 10);
System.out.println(resultado[1] == 2);
System.out.println(resultado[2] == 2016);
}
}
Esse código pode confundir um pouco, pois antes de implementar todo o procedimento somaDias
já se tem o corpo principal do programa e um teste. Antecipar testes é uma ótima estratégia
para pensar como devem ser projetados e que respostas devem dar os procedimentos ou
objetos⁵. Projetar um código longo e que cumpra tantas regras pode levar um tempo, então, em
vez disso, podemos começar decompondo o problema em um subproblema mais simples e que
traz um resultado mais rápido. Considere um procedimento para descobrir o próximo dia de uma
data: Data.amanha(int[]):int[] - que é uma simplificação de soma dias para soma um dia.
Considere os seguintes casos de teste:
⁵Existe uma corrente a favor de primeiro criar o teste unitário e então implementar o código que cumpra o especificado.
O nome desta técnica é Test-Driven Development (TDD, em português desenvolvimento guiado por testes). Para saber mais a
respeito experimente essa leitura http://tdd.caelum.com.br/.
http://tdd.caelum.com.br/
Capítulo 0 – Programação Modular 23
Testes para o procedimento amanha
int[] dataInicio = { 1, 7, 2016};
int[] dataFim = {31, 7, 2016};
// cumprindo primeiro o amanhã (soma um dia)
int[] dataResult = Data.amanha(dataInicio);
System.out.println(dataResult[0] == 2);
System.out.println(dataResult[1] == 7);
System.out.println(dataResult[2] == 2016);
dataResult = Data.amanha(dataFim);
System.out.println(dataResult[0] == 1);
System.out.println(dataResult[1] == 8);
System.out.println(dataResult[2] == 2016);
Fazer a funcionalidade amanha é percorrer 90% do caminho para implementar um somaDias,
que pode ser visto como uma extensão do amanhã, em outras palavras, somar dias é avançar
vários amanhã’s. A seguir a solução do amanha:
Resolvendo o somaDias a partir do amanha
1 class Data {
2 // Jan Fev Mar Abr Mai Jun
3 static int[] diasMes = {31, 28, 31, 30, 31, 30,
4 31, 31, 30, 31, 30, 31};
5 // Jul Ago Set Out Nov Dez
6 static int[] amanha(int[] data) {
7 // copiar o estado
8 int[] amanha = {data[0] + 1, data[1], data[2]};
9 // se ultrapassou o último dia do mês
10 if (amanha[0] > diasMes[amanha[1] - 1]) {
11 amanha[0] = 1; // dia 1
12 // se antes de dezembro
13 if (amanha[1]}
28 return resultado;
29 }
https://git.io/vK9eo
Este exemplo demonstra a estratégia de decomposição do problema em partes menores que
ajudam na solução do problema maior. A simplicidade em saltar um dia é visível nas expressões
https://git.io/vK9eo
Capítulo 0 – Programação Modular 24
condicionais entre as linhas 10 e 19. É possível escrever estas regras de várias formas, a
apresentada foi um exemplo didático. O ponto alto do exemplo está entre as linhas 25 e 27, que
utiliza o amanha como um subprocedimento de somaDias, executado n (ou dias) vezes. Embora
tenha passado nos Casos de Teste, ainda há um bug eminente de uma regra não implementada
para ver no exerício a seguir:
Exercício: corrigir o problema dos anos bissextos
O seguinte teste não passaria Data.amanha(new int[]{28, 2, 2016})[0] == 29.
O dia seguinte a 28/02/2016 deveria ser 29. Falta nas regras o tratamento dos anos
bissextos. Considere corrigir e escrever os Casos de Teste.
A solução apresentada é eficaz, mas não eficiente!
Invocar dezenas ou centenas de vezes o procedimento amanha incorre na criação de
vários vetores temporários descartáveis. O procedimento está longe de ter um bom
desempenho e economia de memória.
Exercício: implementar o procedimento intervalo
Considere dois horários sem a especificação de dia, por exemplo 15:05 e 17:30. Projetar e
implementar um procedimento que calcule o intervalo, também um horário, entre dois
horários informados, por exemplo, entre 15:05 e 17:30 há 2:25. Importante, se inverter-
se os horários o tempo é calculado de 17:30 a 15:05 o que dá 21:35. Use vetores para
representar os horários, por exemplo int[] inicio = {15, 5} (lembre de não usar
o zero antes de minutos e segundos pois muda a representação para octal). Escreva
Casos de Teste suficientes para capturar todos os bugs óbvios em condições normais e
adversas.
Representação de Texto (char e String)
Fechando este tópico, mais uma coisa a considerar: a representação de texto. As linguagens
em geral disponibilizam o tipo string, representado literalmente por caracteres entre aspas,
por exemplo "#poocomhonra". As strings, embora fundamentais nas linguagens, não são
elementares, mas sim compostas por caracteres individuais (em Java um char) e podem ser
vistas como um vetor de caracteres. Assim, elas compartilham as propriedades dos vetores, como
posição e tamanho, conforme exemplo a seguir:
Capítulo 0 – Programação Modular 25
Lendo strings como um vetor
// dica: marque os índices
//012345678901
String hashtag = "#poocomhonra";
// comprimento da string
System.out.println(hashtag.length() == 12);
// primeiro caractere
System.out.println(hashtag.charAt(0) == '#');
// em Java um char é representado
// por aspas simples
System.out.println(hashtag.charAt(4) == 'c');
System.out.println(hashtag.charAt(11) == 'a');
char[] caracteres = hashtag.toCharArray();
As strings assim como os vetores, em Java, são objetos, referenciáveis, mas com uma diferença:
enquanto vetores são mutáveis, strings não são. Essa característica de imutabilidade da string,
e que veremos como fazer com nossos objetos nos capítulos a seguir, garante que as strings
originais não sofram alterações quando passadas para procedimentos ou métodos. Já os vetores,
estão propensos a alterações por onde passam, fenômeno conhecido como efeito colateral do
procedimento (e que vale o mesmo para métodos em OO). Para exemplificar, vamos retomar o
procedimento amanha:
Procedimento amanha com retorno de cópia modificada da entrada
1 class Data {
2 // Jan Fev Mar Abr Mai Jun
3 static int[] diasMes = {31, 28, 31, 30, 31, 30,
4 31, 31, 30, 31, 30, 31};
5 // Jul Ago Set Out Nov Dez
6 static int[] amanha(int[] data) {
7 // copiar o estado
8 int[] amanha = {data[0] + 1, data[1], data[2]};
9 // se ultrapassou o último dia do mês
10 if (amanha[0] > diasMes[amanha[1] - 1]) {
11 amanha[0] = 1; // dia 1
12 // se antes de dezembro
13 if (amanha[1] diasMes[data[1] - 1]) {
data[0] = 1;
if (data[1]