Introdução a Programação Orientada a Objetos (Parte 1)
Este artigo tem por objetivo apresentar a programadores iniciantes o que é o paradigma de programação orientada a objetos (POO). Esta primeira parte apresentará os conceitos básicos desse paradigma, ilustrados com código escrito na linguagem orientada a objetos C+ou- (minha linguagem de programação fictícia que mistura as sintaxes de PHP, Python em Ruby).
Após a conclusão desta série introdutória apresentarei a POO dentro de linguagens populares como PHP5, Python e Ruby.
Antes de entender o mundo dos objetos vamos a algumas questões essenciais.
A POO é obrigatoriamente necessária?
Resposta curta: não.
Resposta longa: depende da linguagem de programação que você estiver utilizando.
Se você estiver programando em PHP, você não será forçado em nenhum momento a utilizar o suporte da linguagem a orientação a objetos (a menos que utilize bibliotecas externas baseadas em classes). Você pode escrever o clássico programa “Hello, world!” em PHP com apenas uma linha (desconsiderando as tags do PHP):
echo "Hello, world!";
Até aí nenhum objeto, certo? echo é uma construção da linguagem, e "Hello, world!"
é algo (diferente de um objeto) do tipo string. O que ele realmente é não nos
importa muito neste momento.
Então PHP conta 1 ponto para a minha resposta curta (não, a POO não é obrigatoriamente necessária). Mas Java conta um ponto para a resposta longa. O mesmo programa “Hello, world!” em Java não sai por menos que isso:
public class Program {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
Em PHP o programa era muito mais simples, certo? O que são todas essas
palavras? public, class? Bom, tudo isso em a ver com a orientação
a objetos nessa linguagem. Ou seja, seu programinha mais básico em
Java não pôde se esconder da POO.
O que é a POO?
A programação orientada a objetos é, antes de tudo, um paradigma de programação: é uma forma, nem mais certa e nem mais errada que as outras, de escrever software. Entre os paradigmas mais conhecidos estão:
- Procedural
- Funcional
- Lógico
- Orientado a Objetos
O paradigma de orientação a objetos é uma evolução do paradigma
procedural. No C, por exemplo, uma linguagem procedural, é
possível utilizar variáveis simples (para armazenar números
inteiros, por exemplo), variáveis mais complexas fornecidas
pela própria linguagem (como o array), e ainda variáveis
complexas definidas pelo próprio usuário (você, o programador):
a estrutura (struct). Uma estrutura é uma variável que
serve como “cabide” para outras variáveis. Vamos imaginar
a variável música como uma estrutura:
música.nome = 'Dogs'
música.autor = 'Damien Rice'
música.duração = '4.14'
Nesse caso a variável música possui 3 variáveis internas:
nome, autor e duração.
Para nos aproximarmos do conceito de objetos e troca de
mensagens vamos começar a evoluir a nossa estrutura música.
Queremos que uma música possua letra, e que essa letra
possa ser impressa a qualquer momento (isso seria, para o
nosso programa, o equivalente a tocar a música). Não queremos
que a nossa letra seja uma grande string, e sim que seja
um conjunto de frases.
Como a nossa linguagem C+ou- suporta arrays, podemos implementar
a letra da música, simplesmente, como uma nova variável dentro
de música:
música.letra[0] = 'Lalala...'
música.letra[1] = 'Lelele...'
Agora, para tocar a música, podemos construir uma simples
função que recebe uma música, itera sobre cada verso da
letra da música e imprime cada um deles, na seqüência em
que foram definidos dentro do array letra.
função tocar_música(mús):
frases = música.letra
para frase em frases:
imprimir frase + "\n";
fim
fim
Aplicando o paradigma de orientação a objetos a esse caso, podemos encarar a música em si como um objeto. Tudo o que a música sabe sobre si mesma (seu nome, seu autor, sua duração e sua letra) são seus atributos. Tudo aquilo que a música sabe fazer (imprimir sua própria letra) são seus métodos (e correspondem às mensagens que esse objeto sabe responder).
E como construímos os nossos próprios objetos? Em geral, as
linguagens orientadas a objetos suportam o conceito de classes
como forma de construção de moldes para objetos. Uma classe
seria uma fôrma, enquanto os objetos seriam os biscoitos que
conseguimos fabricar com essa fôrma. A classe define quais são
os atributos e os métodos que um objeto dessa classe terá
(esse conceito sofre algumas distorções de acordo com a
linguagem que estamos utilizando).
Vamos definir uma classe Música na nossa linguagem C+ou-:
classe Música:
fim
A definição acima é muito simples: criamos uma nova classe,
e essa classe se chama Música. Objetos dessa classe não
armazenam atributos e também não sabem fazer nada (é isso
que a nossa definição está dizendo!).
Toda linguagem orientada a objetos deve prover uma forma de se criar novos objetos a partir de uma classe. O termo instância, neste contexto, é utilizado para denotar a criação de um novo objeto de uma classe. Diz-se que foi criada “uma nova instãncia da classe X”.
Na linguagem C+ou-, você cria uma nova instância de uma classe
simplesmente ao colocar () após o nome da classe, como no
exemplo abaixo:
dogs = Música()
Neste exemplo, a variável dogs foi definida como uma instância
de Música. Se tentarmos definir o nome dessa música, a seguinte
instrução falharia, pois nossa classe não possui ainda nenhum
atributo:
dogs.nome = 'Dogs'
E aqui entra uma questão fundamental, que define como uma linguagem OO é implementada e como ela pode ser utilizada: é possível acessar os atributos de um objeto diretamente ou não? É possível definir novos atributos para um objeto dinamicamente ou não?
Encapsulamento
O encapsulamento consiste na idéia
de esconder do usuário do objeto os detalhes de sua implementação.
Por exemplo: podemos armazenar a letra das nossas músicas como
um array ou como uma grande seqüência de caracteres (string). O
usuário desse objeto, em teoria, não deveria se preocupar com isso.
Ele deve querer apenas adicionar novos versos à sua música.
Vamos definir nossa classe Música (e, por conseqüência, nossas
novas instâncias de Música) com o atributo não-encapsulado
(público) letra (note que todos os atributos de uma classe C+ou-
são prefixados dentro da classe com uma arroba (@)), e vamos definir
o método imprima_letra também como público.
classe Música:
atributo público @letra = [] // o valor padrão de @letra
// é um array sem elementos - []
método público imprima_letra():
para frase em @letra:
imprimir frase + "\n";
fim
fim
fim
O seguinte código funcionará:
dogs = Música()
dogs.letra = 'Lala'
Mas note que dentro da classe Música, @letra é um array,
mas como o acesso a esse atributo está público, foi permitido
que essa variável fosse definida com um valor string. Agora
a nossa implementação do método imprima_letra(), que espera
que @letra seja um array, vai falhar.
Esse é um dos casos onde o encapsulamento se mostra útil: podemos
proteger o acesso ao atributo @letra com um método público,
que recebe uma string. Note como utilizaríamos a classe Música:
dogs = Música()
dogs.adicione_verso('Lalala')
dogs.adicione_verso('Blablabla')
Agora, se quisermos que o atributo @letra mude de um array
para um grande string, ou se quisermos ainda implementá-lo
como uma instância da classe Letra, o código que usa
a classe Música continuará funcionando, pois os detalhes
de implementação estão escondidos de quem quer que esteja
utilizando uma instância da classe Música.
O encapsulamento é um assunto bastante abrangente. Você precisa estudá-lo a fundo para entender em que situações você deve utilizá-lo e quando é mais prático ignorá-lo.
Construtores
Toda linguagem OO fornece um meio de se inicializar uma
instância de uma classe com um conjunto de valores para seus
atributos internos. Essa funcionalidade é alcançada, em todas
as linguagens OO que conheço, com um método da classe que
possui um nome especial. Em C+ou-, esse método é o __construtor__.
Esse método é “mágico”, pois é invocado sempre que queremos
criar uma nova instância de uma classe. Embora sirva para construir
um novo objeto, esse método especial é chamado normalmente de
construtor de classe (em inglês, class constructor).
classe Música:
atributo público @nome
atributo público @autor
atributo público @duração
atributo público @letra = []
método público __construtor__(nome, autor, duração)
@nome = nome
@autor = autor
@duração = duração
fim
método público imprima_letra():
para frase em @letra:
imprimir frase + "\n";
fim
fim
fim
Agora podemos criar uma nova instância de Música da seguinte
forma:
dogs = Música('Dogs', 'Damien Rice', 4.14)
'Dogs', 'Damien Rice' e 4.14 são encarados pela C+ou- como
os parâmetros passados para o construtor da classe. Antes de retornar
uma nova instância de Música, a linguagem invoca o método __construtor__
de Música, passando esses três parâmetros. Esse método é declarado público
para que possa ser invocado pelos mecanismos da linguagem.
A implementação do método é bastante simples: vinculamos cada um dos valores passados como argumento aos atributos internos do objeto.
Também definimos, a partir de agora, que os atributos nome, autor e
duração são públicos, para que possamos acessá-los externamente. Eles
não estão, portanto, encapsulados. O seguinte código, agora, será válido:
dogs = Música('Dogs', 'Damien Rice', 4.14)
imprimir dogs.nome + "\n"
imprimir dogs.autor + "\n"
imprimir dogs.duração + "\n"
A saída do programa será:
Dogs
Damien Rice
4.14
Para saber mais sobre construtores é mais útil consultar a documentação referente a uma linguagem de programação específica. Assim você fica conhecendo qual o nome que a linguagem utiliza para o construtor, e como ele pode ser invocado. A título de curiosidade:
- Em Java o construtor da classe é um método com o mesmo nome da classe
- Em PHP5 o construtor da classe é o método
__construct - Em Python o construtor da classe é o método
__init__ - Em Ruby o construtor da classe é o método
initialize
Considerações a respeito de nomenclatura
Embora eu tenha definido uma classe como um molde para a criação de novos objetos, em algumas linguagens ela pode ser muito mais que isso. Ruby, por exemplo, trata a própria classe como um objeto, a qual é capaz de criar instâncias de si mesma. Para PHP e Java, por outro lado, a classe não é um objeto (embora possa responder a certas mensagens, e confundindo a cabeça de quem tenta entender a linguagem).
É importante ter em mente, portanto, que a orientação a objetos não é vista por todas as linguagens da mesma forma. Pelo contrário, cada linguagem a implementa da forma que considera mais produtiva e/ou mais eficiente. Quando se aprende uma nova linguagem OO deve-se procurar entender o que são classes e objetos na visão dela, e até onde vão suas responsabilidades e funcionalidades.
Na próxima parte…
Na próxima parte desta série veremos:
- Modificadores de acesso
- Herança
- Passagens de parâmetros por cópia e por referência
- Encadeamento de chamadas de métodos
Se ainda houver interesse da parte dos leitores, a terceira e (talvez) última parte desta série introdutória abordará a programação funcional dentro das linguagens OO, e como a junção desses dois paradigmas pode render programas mais legíveis e poderosos.