Introdução a Programação Orientada a Objetos (Parte 1)

Em: 30/12/2007 Tags: OOP Comentários (8) Referencie do seu blog (Trackback)

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.


Versão 5.3 do PHP terá namespaces

Em: 22/12/2007 Tags: PHP Comentários (0) Referencie do seu blog (Trackback)

Descobri hoje (um pouco atrasado) que em novembro entrou no branch 5.3 do repositório do PHP o código que trará para a linguagem o poder dos namespaces. Os namespaces existem em linguagens populares como C++, Ruby (na forma de módulos) e Java (na forma de packages) e sempre fizeram falta no PHP.

A partir da leitura de outros blogs, me parece que a utilização de namespaces será mais ou menos assim (utilizo MyProject::StringHelpers como um namespace hipotético):

// o namespace MyProject::StringHelpers será utilizado até
// o fim do arquivo
namespace MyProject::StringHelpers;

function camelize($str) {
}

function humanize($str) {
}

class Foo {
}

// utilizando funções e classes definidas no namespace
// MyProject::StringHelpers (notação longa)
MyProject::StringHelpers::camelize('php_with_namespaces');
$foo = new MyProject::StringHelpers::Foo();

// importando o namespace para o escopo global não precisamos
// mais utilizar o prefixo para acessar suas funções
use MyProject::StringHelpers;
humanize('php_with_namespaces');

Além de funções será possível definir classes e constantes dentro de um namespace.

Fiquei realmente surpreso ao saber que teremos essa novidade no PHP. Embora seja uma característica importantíssima e manjada, já havia lido previsões assustadoras de que namespaces não estariam presentes nem no PHP 6.

O que nos aguarda? Funções de primeira classe? Eu acredito que sim. Entrei hoje na lista de discussão dos desenvolvedores do PHP e há conversas sobre closures. Quem sabe o PHP 6 será uma nova revolução para a linguagem, com a adição de características funcionais que tanto tem impulsionado o Ruby e o JavaScript nos últimos tempos (acho que o Java também terá closures em sua próxima versão).

Vou tentar instalar a versão 5.3 no meu Ubuntu a partir dos snapshots, e volto a tratar das novidades do PHP assim que puder.

Ah, o mais importante: a versão 5.3 é aguardada para o início de 2008.


Planet PHP

Em: 22/12/2007 Tags: PHP Comentários (0) Referencie do seu blog (Trackback)

Descobri apenas hoje o Planet PHP, “meta blog” que agrega feeds de vários blogs sobre PHP (todos em inglês). Vale a pena adicionar o feed ao seu leitor favorito.


Uma função PHP por dia

Em: 21/12/2007 Tags: PHP Comentários (0) Referencie do seu blog (Trackback)

Descobri no blog do Raphael de Almeida o ótimo site Funcaday. A idéia é simples: explicar, a cada dia, a finalidade de uma função PHP. A escolha das funções parece ser aleatória, e para não ter que voltar ao site diariamente os criadores disponibilizam um feed.


Projetando funções mais elegantes em PHP

Em: 19/12/2007 Tags: PHP, Ruby Comentários (2) Referencie do seu blog (Trackback)

Uma das facilidades óbvias que o PHP nos dá para definir funções é a possibilidade de se definir valores padrão para os parâmetros. Acredito, porém, que esse é o “lado negro” da força. Se uma função, por exemplo, deve receber uma grande quantidade de parâmetros, e ao menos 4 ou 5 são opcionais, é bem provável que você cairá no seguinte dilema: qual dos parâmetros aqui é mais importante?

Vamos a um exemplo nada típico para PHP. Imagine que você quer criar uma biblioteca gráfica. Sua pequena biblioteca deverá ser capaz de desenhar círculos, e para isso você decidiu implementar a função draw_circ(). O que sabemos sobre um círculo? Todo círculo tem, por exemplo, um raio. Isso já é alguma coisa. Então você escreve sua definição:

function draw_circ($radius) { ... }

Um círculo tem raio… ok… mas com essa definição todos os meus círculos serão idênticos. Precisamos de mais parâmetros nessa função. Podemos adicionar uma cor de fundo, uma cor para a “borda” e uma largura para a “borda”. Mas será que, sempre que utilizar minha nova função, desejarei especificar essas 4 características? Não, certamente não. Então podemos declarar esses 3 novos parâmetros como opcionais, pois daremos valores padrão a eles.

function draw_circ($radius, $bg_color='white', $border_color='black', $border_width='1px') { ... }

Mas acabamos de criar um problema do ponto de vista da beleza do código. Como poderemos criar um círculo de raio 3, com cor de fundo branca, cor da borda preta, mas com largura da borda de 3px? Ou seja, como criar um círculo com todos os parâmetros definidos com valores padrão, mas apenas com o quarto parâmetro assumindo um valor diferente? Não é difícil, é verdade:

draw_circ(3, null, null, '3px');

A beleza do código é comprometida com esses dois parâmetros null. Pois neste caso nossa função recebe apenas 4 parâmetros. Mas na vida real muitas vezes nossas funções recebem 8, 9, ou mais. Com que solução contamos, em PHP, para deixar esse código mais apresentável?

Minha abordagem favorita (que você talvez não goste) é emprestada de Ruby. Ela consiste na utilização de um array associativo, que atua como um agregador de todos os parâmetros opcionais. Com essa nova abordagem podemos definir nossa função com apenas 2 parâmetros:

function draw_circ($radius, $options=array()) { ... }

Continuamos tendo o raio como o único parâmetro obrigatório. Os demais passam a ser opcionais, já que o nosso array associativo de opções é, ele próprio, opcional.

Mesmo com essa nova definição é possível escrever a implementação dessa função de forma equivocada. Esta seria uma má implementação para o tratamento das opções passadas para $options:

function draw_circ($radius, $options=array()) {
    $options['bg_color'] = isset($options['bg_color']) ? $options['bg_color'] : 'white';
    $options['border_color'] = isset($options['border_color']) ? $options['border_color'] : 'black';
    $options['border_width'] = isset($options['border_width']) ? $options['border_width'] : '1px';
    // ...
}

Por que ela é equivocada?

  • É verbosa demais
  • Trata os parâmetros de forma individual
  • Deixa a legibilidade do código a desejar

O que essa implementação faz é o seguinte: para cada chave válida em $options, verifica se há um valor definido para essa chave. Se há, então o atribui novamente na mesma chave de $options. Senão, atribui o valor padrão àquela chave.

A função array_merge() chega, então, para salvar o dia e garantir a legibilidade desse código. De acordo com a documentação oficial, essa função faz o seguinte:

array_merge() funde os elementos de um ou mais arrays para que os valores de um sejam adicionados no fim do array anterior. Ela retorna o array resultante.

Até aí nada de mais. Essa função parece legal, mas serve para nós? O texto continua:

Se os arrays de entrada possuem as mesmas chaves string, então o segundo valor definido para aquela chave irá sobrescrever o primeiro.

Opa! É disso que precisamos.

Veja como fica a nova implementação:

function draw_circ($radius, $options=array()) {
    $defaults = array(
        'bg_color'     => 'white',
        'border_color' => 'black',
        'border_width' => '1px'
    );
    $options = array_merge($defaults, $options);
    // ...
}

draw_circ(5, array('bg_color' => 'orange'));

A idéia é muito simples: $defaults é o nosso array associativo que possui os valores padrão para cada uma das opções possíveis. É importante que todos os valores padrão sejam declarados nesse array. Após sua definição utilizamos array_merge() para sobrescrever cada um dos valores definidos em $defaults pelos respectivos valores definidos em $options. Uma chave de $defaults não definida por $options permanecerá com seu valor original (ou seja, seu valor padrão ou default). Como array_merge() não modifica o array original, precisamos atribuir seu resultado à antiga variável $options, agora com valores sobrescritos.

Com essa implementação ganhamos muito em legibilidade e em beleza do código. O grande problema fica para a notação de arrays literais em PHP. Enquanto a definição da função draw_circ() fica muito legal uma vez que utilizamos array_merge(), a chamada a essa função precisa ter a construção array(). Esse é o preço que se paga. Você pode escolher entre esta implementação ou continuar com as dezenas de variáveis com valores padrão e, de vez em quando, ver suas chamadas de função recebendo valores null intercalados com valores verdadeiros.

Bonus

Ficou curioso para saber como draw_circ poderia ser implementada em Ruby? Vamos ao código:

def draw_circ(radius, options={})
  defaults = {
    :bg_color     => 'white',
    :border_color => 'black',
    :border_width => '1px'
  }
  options = defaults.merge(options)
  options.each {|key,value| puts "#{key} => #{value}" }
end

draw_circ(5, :bg_color => 'orange')

A última linha da implementação não faz parte da função, foi colocada apenas para mostrar que a saída é aquela que esperamos:

bg_color => orange
border_color => black
border_width => 1px

Evitando o loop for no PHP

Em: 19/12/2007 Tags: PHP Comentários (4) Referencie do seu blog (Trackback)

Após conhecer outras linguagens me dei conta de que o PHP não tinha (ou eu não conhecia) uma função ou construção que me permitisse criar um array a partir de uma seqüência. Ruby, por exemplo, nos dá a classe Range. Mas não é que o PHP oferece uma solução similar? Uma salva de palmas para a função range(), por favor.

Exemplo: queremos um array com todos os números entre 5 e 15.

$arr = range(5,15);

Você ainda pode utilizar ranges de caracteres. Por exemplo, de ‘c’ a ‘r’.

$chars = range('c', 'r');

Mas o que me chamou atenção foi descobrir, após 4 anos de programação PHP, que a construção foreach pode ser combinada com range(). Isso torna um loop feio e arcaica como este…

for ($i = 1; $i <= 10; $i++) { ... }

… em um loop foreach muito mais agradável:

foreach (range(1, 10) as $n) { ... }

É verdade, a verbosidade continua grande. Seria interessante se tivéssemos no PHP algo como o times do Ruby, ou mesmo each. Mas esqueça. Essas construções só funcionam bem no Ruby (e no JavaScript, com Prototype), pois essas linguagens tratam funções como valores de primeira classe. O PHP deve continuar deixando as funções para escanteio em sua versão 6, então vamos ter que continuar com nosso código ultra verboso.