O que é Programação Orientada a Objetos

Programação orientada a objetos surgiu como uma alternativa ao paradigma de programação dominante até então, a chamada programação procedural. Em programação procedural, o programa é uma sequência de passos: faça isso, depois faça aquilo, e assim por diante. Esse paradigma de programação funciona bem em muitos casos, mas apresenta desvantagens à medida em que programas crescem.

Imagine, por exemplo, que você está escrevendo um programa para exibir formas geométricas na tela do computador. A princípio, seu programa precisa lidar com dois tipos de formas: círculos e quadrados, que estão armazenados em uma lista contendo todas as formas geométricas a serem exibidas na tela. Uma maneira de escrever um programa para realizar esta tarefa seria o mostrado abaixo.

for forma_geom in formas_geometricas:
    if circulo(forma_geom):
        desenha_circulo()
    elif quadrado(forma_geom):
        desenha_quadrado()

Agora suponha que seu programa precise ser estendido de modo a ser capaz de exibir outros tipos de formas geométricas como triângulos, retangulos e elipses. A maneira natural de alterar o código acima para fazer isso seria como mostrado no exemplo a seguir.

for forma_geom in formas_geometricas:
    if circulo(forma_geom):
        desenha_circulo()
    elif quadrado(forma_geom):
        desenha_quadrado()
    elif triangulo(forma_geom):
        desenha_triangulo()
    elif retangulo(forma_geom):
        desenha_retangulo()
    elif elipse(forma_geom):
        desenha_elipse()

O código acima parece razoável, mas note um problema. Toda vez que o programa precisa ser capaz de exibir um novo tipo de forma geométrica, temos que fazer duas coisas: criar a funcionalidade para exibir a nova forma geométrica, e modificar o programa principal (que percorre a lista de formas e as exibe na tela) de modo a alterar a sequência de comandos if e elif para adicionar uma nova forma.

Idealmente, ao adicionar uma nova forma geométrica, deveríamos precisar somente criar a funcionalidade para exibir essa nova forma. O programa principal, que desenha as várias formas na tela, não deveria precisar ser modificado. Como fazer isso?

Perceba que o foco do programa acima é procedural: ele verifica cada tipo de forma geométrica para então decidir qual deverá ser exibida e invoca a função correspondente para exibir aquele tipo de forma geométrica. Em outras palavras, a lógica do programa é toda atrelada ao procedimento de selecionar e exibir uma dada forma geométrica.

No paradigma de programação orientada a objetos (OOP, da sigla em inglês), programa são coleções de objetos e a lógica de um programa passa a ser mais atrelada aos objetos do programa. Em outras palavras, em OOP a inteligência dos programas está embutida nos objetos.

Assim, ao implementarmos nosso exemplo anterior usando o paradigma de OOP, cada forma geométrica "saberia" se desenhar na tela do computador. Como resultado, o programa principal seria simplificado e se tornaria mais ou menos como mostrado abaixo.

for forma_geom in formas_geometricas:
    forma_geom.desenha()

Com isso, para adicionar uma nova forma geométrica ao programa, só precisamos implementar o recurso de a forma geométrica se exibir na tela. O programa principal não precisa ser alterado.

Esta é uma das vantagens de OOP. E ela pode parecer uma vantagem pequena à primeira vista, mas à medida em que nossos programa se tornam maiores e mais complexos, OOP facilita muito as tarefas de manter e estender esses programas.

Anteriormente, dissemos que em OOP a lógica é embutida nos objetos do programa e que programas são coleções de objetos que interagem entre si. Essas declarações (assim como o próprio nome "programação orientada a objetos") sugere que objetos são o centro deste paradigma de programação. Logo, precisamos entender o que são objetos, como criá-los e manipulá-los.

Objetos e Classes

Em OOP, um objeto é uma forma de armazenarmos um estado e comportamentos associados a esse estado. Objetos possuem um tipo, uma representação interna (campos), e provêm uma interface (métodos). As informações (campos) de um objeto são chamadas de atributos do objeto, e as operações realizadas sobre o objeto são chamadas métodos do objeto/classe.

Imagine, por exemplo, que tenhamos um objeto que representa um carro. O estado do objeto consistiria de coisas como marca, modelo, chassi, ano, etc. E o comportamento seria as funcionalidades que um carro normalmente possui, como ligar, desligar, acelerar, frear, etc.

No nosso exemplo de formas geométricas, o estado de uma forma são as informações associadas a ela (o valor raio, no caso de círculos, o tamanho do lado, no caso de quadrados, e assim por diante), ao passo que o comportamento seria a função que exibe a forma geométrica na tela do computador.

Uma outra maneira de entendermos objetos é como instâncias de uma classe.

Em OOP, uma classe é uma declaração dos estados que um certo tipo de objeto armazena e uma implementação dos comportamentos associados a esse tipo de objeto. Mesmo sem saber, você já deve ter utilizado classes antes, visto que elas estão por toda parte em Python. Por exemplo, quando você invoca o método reverse() para inverter os elementos de uma lista, está invocando um método de uma classe da biblioteca padrão Python.

No caso das formas geométricas, podemos ter uma classe que representa um círculo, na qual como estado temos o valor do raio do círculo e como comportamento temos a função (método, no linguajar de OOP) desenha() e a função imprime(), criada por conveniência. Em Python, podemos criar esta classe assim.

class Circulo: (1)
    def __init__(self, r): (2)
        self.raio = r (3)

    (4)

    def desenha(self):
        pass

    def imprime(self):
        print("Círculo de raio {}". format(self.raio))
1 Nomes de classes não pertencentes à biblioteca padrão de Python começam com letra maiúscula, por convenção.
2 A função init é invocada todas as vezes que um objeto da classe é criado.
3 A variável raio é o estado armazenado em todo objeto do tipo Circulo que for criado em nosso programa. O estado de um objeto também é chamado de variáveis da classe, campos da classe ou atributos da classe.
4 As funções desenha() e imprime() contêm o comportamento de objetos do tipo Circulo, ou seja, contêm as operações que objetos desse tipo sabem realizar. O comportamento de objetos de um determinado tipo também recebe o nome de métodos da classe. Em linhas gerais, um método é uma função que funciona somente dentro da classe, isto é, somente quando invocado por objetos da classe, seguindo uma sintaxe específica: objeto.metodo. Além disso, ao invocar um método, Python sempre passa como primeiro parâmetro para o método o objeto que está invocando o método. Para indicar isso, a convenção é declarar self como primeiro parâmetro de todo método, como fizemos ao declarar os métodos desenha() e imprime.

O que a declaração da classe Circulo acima nos diz é que objetos do tipo Circulo terão um raio, definido no momento da criação do objeto, método que desenha o círculo na tela, e outro que imprime as informações do círculo.

A pergunta que costuma surgir neste momento é: Como criar objetos da classe Circulo?

Lembre-se que dissemos que um objeto é uma instância de uma classe. Logo, para criar um objeto da classe Circulo precisamos criar uma instância da classe, provendo um valor para o raio do círculo. O exemplo abaixo mostra como fazer isso.

obj_circulo = Circulo(3) (1)
obj_circulo.imprime() (2)
Círculo de raio 3
1 Cria um objeto da classe Circulo cujo raio mede três unidades.
2 Invoca a função imprime() do objeto recém-criado.

No código que declara a classe Circulo, usamos a palavra-chave self várias vezes. Porém, ao criar e manipular objetos da classe Circulo não tivemos que usar essa palavra-chave. Afinal, o que significa a palavra-chave self e para quê ela é utilizada?

Ao criar uma classe, estamos criando um estado e comportamento associado a objetos desta classe. Em Python, a forma de dizermos que um estado ou comportamento está associado à classe em questão é por meio da palavra-chave self. Assim, a declaração objeto.medoto(self, params) seria o equivalente de dizermos metodo(objeto, params). Em outras palavras, o self nos diz que o estado ou método está associado ao objeto sendo manipulado.

Agora que já entendemos os conceitos básicos de OOP em Python, podemos avançar para conceitos mais complexos e refinados. Nas próximas seções, cobriremos três conceitos que são princípios-chave em OOP:

  • Herança: como estender a funcionalidade de classes existentes.

  • Encapsulamento: como criar classes de modo a esconder certos tipos de informação e expor somente aquilo que é essencial para o uso da classe.

  • Polimorfismo: como criar classes com uma interface unificada, classes que se comportam como as classes já existentes em Python.