Fundamentos de Programação Orientada a Objetos em Python

Conceitos Básicos de Programação Orientada a Objetos

Todo programador moderno já ouviu falar em "programação orientada a objetos". Frequentemente, as pessoas pedem para explicar o que é "programação orientada a objetos" em uma única frase. Vamos primeiro examinar a definição mais formal:

Agrupar estruturas de dados e os métodos que as manipulam em objetos (object), classificar objetos com comportamentos semelhantes em classes (class), através do encapsulamento (encapsulation) ocultar detalhes internos, através da herança (inheritance) especializar (specialization) e generalizar (generalization) classes, e através do polimorfismo (polymorphism) implementar despacho dinâmico baseado no tipo do objeto.

Essa definição pode não ser muito clara. Vamos então ver uma explicação mais acessível, como o conteúdo abaixo:

Nota: O conteúdo acima é da internet e não representa as opiniões do autor, não está relacionado com a posição do autor, e o autor não se responsabiliza por ele.

Anteriormente, mencionamos que "um programa é uma coleção de instruções". As instruções que escrevemos em um programa são executadas como uma ou mais instruções pelo CPU. Para simplificar o design de programas, introduzimos o conceito de função, colocando código relativamente independente e frequentemente reutilizável em funções, que podem ser chamadas quando necessário. Se uma função for muito complexa e volumosa, podemos dividi-la em subfunções para reduzir a complexidade do sistema.

No entanto, independentemente de tudo isso, não se percebeu que a programação é, essencialmente, o programador controlando o computador para realizar várias tarefas de acordo com a maneira como o computador trabalha. O modo de operação do computador é diferente do modo de pensamento normal dos humanos, e a programação exige que abandonemos o pensamento humano normal para acomodar o computador, reduzindo o prazer da programação. Declarações como "todos deveriam aprender programação" permanecem apenas como palavras.

Além disso, o mais importante é que quando precisamos desenvolver um sistema complexo, a complexidade do código torna o desenvolvimento e a manutenção difíceis. Por isso, no final dos anos 1960, conceitos como "crise de software", "engenharia de software" e outros começaram a aparecer na indústria.

É claro que os programadores sabem que na realidade não existe uma "bala de prata" que resolva os problemas mencionados acima. O que realmente deu esperança aos desenvolvedores de software foi o conceito de programação orientada a objetos introduzido na linguagem de programação Smalltalk, que surgiu na década de 1970 (os primeiros indícios da programação orientada a objetos podem ser rastreados até a linguagem Simula, mais antiga). Seguindo essa filosofia de programação, dados e funções que manipulam dados em um programa são uma unidade lógica, chamada de "objeto", e a maneira de resolver problemas é criar os objetos necessários e enviar várias mensagens a eles. O trabalho colaborativo de múltiplos objetos nos permite construir sistemas complexos para resolver problemas do mundo real.

Nota: É claro que a programação orientada a objetos não é a "bala de prata" que resolve todos os problemas no desenvolvimento de software. Por isso, as linguagens de programação de alto nível de hoje oferecem suporte a múltiplos paradigmas de programação, e Python não é exceção.

Classes e Objetos

Em termos simples, uma classe é o blueprint ou modelo para objetos, enquanto objetos são instâncias de classes. Essa explicção pode parecer como se estivesse usando conceitos para explicar conceitos, mas pelo menos podemos ver que a classe é um conceito abstrato, enquanto o objeto é algo concreto. No mundo da programação orientada a objetos, tudo é objeto, os objetos têm atributos e comportamentos, cada objeto é único e pertence a uma classe (tipo). Quando extraímos as características estáticas (atributos) e dinâmicas (comportamentos) de um grande número de objetos com características comuns, podemos definir algo chamado "classe".

Definindo Classes

Em Python, podemos usar a palavra-chave class para definir uma classe. Dentro da classe, podemos usar as funções que aprendemos anteriormente para definir métodos, descrevendo assim as características dinâmicas dos objetos. O código a seguir demonstra isso:

class Estudante(object):
    
    # __init__ é um método especial usado para inicializar objetos ao criá-los
    # Por meio deste método, podemos vincular os atributos nome e idade ao objeto estudante
    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade
    
    def estudar(self, nome_curso):
        print('%s está estudando %s.' % (self.nome, nome_curso))
    
    # PEP 8 exige que identificadores usem letras minúsculas, com múltiplas palavras conectadas por sublinhados
    # Mas muitos programadores e empresas preferem usar a convenção de nomenclatura CamelCase
    def assistir_filme(self):
        if self.idade < 18:
            print('%s só pode assistir ao "Bear's Big Adventure".' % self.nome)
        else:
            print('%s está assistindo a um filme romântico.' % self.nome)

Nota: As funções escritas em uma classe são geralmente chamadas de métodos (do objeto). Esses métodos são as mensagens que o objeto pode receber.

Criando e Usando Objetos

Depois de definir uma classe, podemos criar objetos e enviar mensagens a eles da seguinte maneira:

def principal():
    # Criar objeto estudante com nome e idade especificados
    estudante1 = Estudante('João', 38)
    # Enviar mensagem de estudo para o objeto
    estudante1.estudar('Design de Programas em Python')
    # Enviar mensagem de assistir filme para o objeto
    estudante1.assistir_filme()
    estudante2 = Estudante('Maria', 15)
    estudante2.estudar('Ética')
    estudante2.assistir_filme()


if __name__ == '__main__':
    principal()

Problema de Visibilidade de Acesso

Para o código acima, programadores com experiência em C++, Java, C# e outras linguagens podem perguntar quais são as permissões de acesso (também chamadas de visibilidade) dos atributos nome e idade vinculados ao objeto Estudante. Em muitas linguagens de programação orientada a objetos, geralmente definimos os atributos dos objetos como privados (private) ou protegidos (protected), ou seja, não permitimos o acesso do exterior, enquanto os métodos dos objetos geralmente são públicos (public), pois métodos públicos são as mensagens que o objeto pode receber. Em Python, os atributos e métodos têm apenas dois tipos de acesso: público e privado. Se você deseja que um atributo seja privado, pode usar dois sublinhados no início do nome do atributo. O código a seguir pode demonstrar isso:

class Teste:
    
    def __init__(self, foo):
        self.__foo = foo
    
    def __bar(self):
        print(self.__foo)
        print('__bar')


def principal():
    teste = Teste('olá')
    # AttributeError: objeto 'Teste' não tem atributo '__bar'
    teste.__bar()
    # AttributeError: objeto 'Teste' não tem atributo '__foo'
    print(teste.__foo)


if __name__ == "__main__":
    principal()

No entanto, Python não garante estritamente a privacidade de atributos ou métodos em termos de sintaxe. Ele apenas renomeia atributos ou métodos privados para "dificultar" o acesso a eles. Na verdade, se você conhece a regra de renomeação, ainda pode acessá-los. O código a seguir demonstra isso. Essa configuração pode ser explicada pela frase famosa: "We are all consenting adults here". A maioria dos programadores acredita que a abertura é melhor que o fechamento, e os programadores devem ser responsáveis por suas próprias ações.

class Teste:
    
    def __init__(self, foo):
        self.__foo = foo
    
    def __bar(self):
        print(self.__foo)
        print('__bar')


def principal():
    teste = Teste('olá')
    teste._Teste__bar()
    print(teste._Teste__foo)


if __name__ == "__main__":
    principal()

Na prática, não recomendamos definir atributos como privativos, pois isso impediria que subclasses os acessassem (abordaremos isso mais tarde). Portanto, a maioria dos programadores Python segue uma convenção de nomenclatura: usar um único sublinhado no início do nome do atributo para indicar que o atributo é protegido. Código fora dessa classe deve acessar esses atributos com cautela. Essa prática não é uma regra de sintaxe; atributos e métodos que começam com um único sublinhado ainda podem ser acessados do exterior. Na verdade, eles são mais uma sugestão ou metáfora. A respeito disso, você pode consultar meu artigo "Python - Os Armadilhos que Encontramos ao Longo dos Anos".

Pilares da Programação Orientada a Objetos

A programação orientada a objetos tem três pilares: encapsulamento, herança e polimorfismo. Os últimos dois conceitos serão explicados detalhadamente no próximo capítulo. Vamos primeiro explicar o que é encapsulamento. Minha própria compreensão de encapsulamento é "ocultar todos os detalhes de implementação que podem ser ocultados, expondo apenas uma interface de programação simples aos usuários externos". Os métodos que definimos em uma classe são, na verdade, a encapsulação de dados e operações neles. Depois de criarmos objetos, basta enviar uma mensagem ao objeto (chamar o método) para executar o código no método. Em outras palavras, só precisamos saber o nome do método e os parâmetros passados (visão externa do método), sem precisar saber os detalhes de implementação internos do método (visão interna do método).

Exercícios

Exercício 1: Definir uma classe descrevendo um relógio digital
class Relogio(object):
    """Relógio digital"""
    
    def __init__(self, hora=0, minuto=0, segundo=0):
        """Método de inicialização
        
        :param hora: hora
        :param minuto: minuto
        :param segundo: segundo
        """
        self._hora = hora
        self._minuto = minuto
        self._segundo = segundo
    
    def avancar(self):
        """Avançar o tempo"""
        self._segundo += 1
        if self._segundo == 60:
            self._segundo = 0
            self._minuto += 1
            if self._minuto == 60:
                self._minuto = 0
                self._hora += 1
                if self._hora == 24:
                    self._hora = 0
    
    def mostrar(self):
        """Mostrar a hora"""
        return '%02d:%02d:%02d' % \
               (self._hora, self._minuto, self._segundo)


def principal():
    relogio = Relogio(23, 59, 58)
    while True:
        print(relogio.mostrar())
        sleep(1)
        relogio.avancar()


if __name__ == '__main__':
    principal()

Exercício 2: Definir uma classe descrevendo um ponto no plano e fornecer métodos para mover o ponto e calcular a distância para outro ponto.
from math import sqrt


class Ponto(object):
    
    def __init__(self, x=0, y=0):
        """Método de inicialização
        
        :param x: coordenada x
        :param y: coordenada y
        """
        self.x = x
        self.y = y
    
    def mover_para(self, x, y):
        """Mover para uma posição específica
        
        :param x: nova coordenada x
        :param y: nova coordenada y
        """
        self.x = x
        self.y = y
    
    def mover_por(self, dx, dy):
        """Mover por um incremento específico
        
        :param dx: incremento na coordenada x
        :param dy: incremento na coordenada y
        """
        self.x += dx
        self.y += dy
    
    def distancia_para(self, outro):
        """Calcular a distância para outro ponto
        
        :param outro: outro ponto
        """
        dx = self.x - outro.x
        dy = self.y - outro.y
        return sqrt(dx ** 2 + dy ** 2)
    
    def __str__(self):
        return '(%s, %s)' % (str(self.x), str(self.y))


def principal():
    p1 = Ponto(3, 5)
    p2 = Ponto()
    print(p1)
    print(p2)
    p2.mover_por(-1, 2)
    print(p2)
    print(p1.distancia_para(p2))


if __name__ == '__main__':
    principal()

Nota: As ilustrações neste capítulo são do livro "Análise e Design Orientados a Objetos" de Grady Booch e outros, um clássico sobre programação orientada a objetos. Os interessados podem comprar e ler este livro para saber mais sobre programação orientada a objetos.

Tags: Python Programação Orientada a Objetos classes e objetos

Publicado em 8-9 05:38