Programação Orientada a Objeto (POO)

Oque é Orientação a Objetos

Primeiramente, o conceito de objeto (coisa) na "vida real" já é bem definido e compreendido por todos nós. Por exemplo, imagine uma caneca, você sabe que todas irão possui altura, diâmetro e profundidade. Essas serão as características principais que definem uma caneca. Além disso, elas irão ter certas funcionalidades como: preencher o interior com café!

Perceba que todo objeto pode ser definido com propriedades e funcionalidades específicas.

Porque (POO) é importante para programadores? ☕

Inserir conceitos de objetos reais ou "não reais" dentro de uma máquina e conseguir estabelecer uma certa interação entre eles não é nada fácil. A capacidade de abstração será muito útil para tarefas como essa. Pois, além de ter que estabelecer uma linguagem entendível com a máquina, qualquer ser humano que olhar pro seu código deverá compreender, ou seja, uma "conversa" com ambas as partes.

POO é uma forma de se expressar, é quase uma filosofia.


Tá, mas como que isso funciona na prática? (com Python 🐍)

class Caneca:
    def __init__(self, cor, material, capacidade):
        self.cor = cor
        self.material = material
        self.capacidade = capacidade
        self.volume_atual = 0

    def descrever(self):
        return f"Uma caneca {self.cor} feita de {self.material} com capacidade de {self.capacidade} ml."

    def encher(self, volume):
        if volume <= 0:
            return "Adicione um volume positivo."
        if self.volume_atual + volume <= self.capacidade:
            self.volume_atual += volume
            return f"A caneca agora tem {self.volume_atual} ml."
        return "A caneca transbordou!"

    def beber(self, volume):
        if volume <= 0:
            return "Beba um volume positivo."
        if volume <= self.volume_atual:
            self.volume_atual -= volume
            return f"Bebeu {volume} ml, {self.volume_atual} ml restantes."
        return "Não há líquido suficiente!"

    def esvaziar(self):
        self.volume_atual = 0
        return "A caneca agora está vazia."

# Exemplo de uso
minha_caneca = Caneca('azul', 'cerâmica', 350)
print(minha_caneca.descrever())  # Uma caneca azul feita de cerâmica com capacidade de 350 ml.
print(minha_caneca.encher(200))  # A caneca agora tem 200 ml.
print(minha_caneca.beber(100))   # Bebeu 100 ml, 100 ml restantes.
print(minha_caneca.esvaziar())   # A caneca agora está vazia.

Como esse código exemplifica POO?

1. Classe

  • em POO, uma classe é um modelo para criar objetos. Define atributos e comportamentos.
  • A classe da canenca é um modelo que define (cor, material, capacidade, volume atual) e métodos (descrever, encher, beber, esvaziar)

2. Objeto

  • É como se fosse uma "cópia" do modelo que pode ser manipulado individualmente.
  • No código: minha_caneca = Caneca('azul', 'cerâmica', 350) cria um objeto específico da classe Caneca. Essa instância (minha_caneca) tem atributos e métodos definidos pela classe Caneca.

3. Encapsulamento

  • É o conceito de esconder os detalhes internos de uma classe e expor apenas oque é necessário.
  • Por exemplo, os métodos encher e beber controlam como os atributos podem ser alterados (como o volume atual) prevenindo um transbordamento ou que se beba mais café do que já tem.

4. Abstração

  • Como eu disse no início, a abstração irá ajudar o programador a simplificar realidades complexas ao se focar nos aspectos mais importantes e IGNORAR os detalhes não essenciais.
  • Eu não precisei me importar com o peso ou o formato da caneca no código.

Esta publicação foi feita enqunato eu estudava, então não espere conceitos avançados e nem nada do tipo. O importante foi compreender a base que é fundamental para todo o aprendizado seja qual for o conteúdo.

  • Dêem uma olhada nessa aula do canal Programação Dinâmica! :D

E lá vamos nós mais uma vez... :-)

Como eu já disse outras vezes, mais precisamente aqui e aqui: não existe uma definição única, canônica e universal sobre Orientação a Objetos (sugiro que leia esses links, e também os outros links que estão lá, em especial este e este). Mas só pra resumir:

Existem várias "escolas"/vertentes de orientação a objeto. Uma delas é essa que vc descreveu, que também se tornou a mais comum - e por isso muita gente acha que é a única, ou a "certa". Leia os links indicados acima para mais detalhes.

E mesmo dentro da vertente mais popular, ainda existem variações e controvérsias. Por exemplo, muitos autores consideram que existem apenas três pilares da orientação a objeto (encapsulamento, herança e polimorfismo). Alguns incluem a abstração como um quarto pilar, outros acham que é um conceito genérico demais e portanto aplicável a qualquer paradigma (e de fato, dá pra abstrair detalhes de implementação em qualquer linguagem ou paradigma, POO não é especial nesse sentido).

O próprio termo "objeto" é amplo e não se restringe a POO - como já citado aqui, cada linguagem define de um jeito. Em C, por exemplo, "objeto" é definido como uma "região de armazenamento de dados do ambiente de execução, cujo conteúdo pode representar valores". Ou seja, nada a ver com a definição "clássica" de POO.

Mesmo o uso de classes não é obrigatório, já que não é a única forma de se obter um código orientado a objeto (independente da vertente escolhida). JavaScript, por exemplo, usa protótipos (até existe a palavra-chave class, mas ela é apenas um syntatic sugar). Na verdade, até mesmo em C dá para fazer um código com polimorfismo, herança e tudo mais, só não é tão conveniente. As classes são apenas um mecanismo que facilita isso, mas não é o único (leitura adicional).

E ter atributos/estado também não é obrigatório, vide as classes utilitárias do Java e C# (como Math, que só têm métodos estáticos), por exemplo. Até mesmo ter atributos e métodos privados é discutível. Em Python, por exemplo, nada é realmente privado.


Sei que estou sendo pedante e que na prática, se o mercado "escolheu" uma das vertentes, então faz todo o sentido que os cursos se foquem em ensinar apenas esta. Mas se a ideia é, como vc mesmo disse, "compreender a base", acho importante pelo menos saber que na verdade o buraco é mais embaixo...

Boa!

Caso possa me auxiliar em uma dúvida:

Digamos sobre Felinos...

Há diversos tipos deles e muitos compartilham características similares.

Qual a melhor forma de trabalhar a lógica nesse caso?

Um exemplo: Os felinos (gato, leopardo e tigre) compartilham as características x e y, porém, a z é uma variável de cada um.

Não tem jeito certo, tudo depende do problema específico que vc quer resolver. Uma ideia inicial (que já sugeriram) é criar classes para cada espécie (classe `Gato`, `Leopardo`, `Tigre`, etc), sendo que todas herdam da classe `Felino`. Aí `Felino` teria os atributos `x` e `y`, e as subclasses teriam o `z`. Mas nada impede que se tenha apenas uma classe `Felino` com os atributos `x`, `y` e `z`, e mais um atributo extra para designar a espécie. O que é melhor? Depende. Sem os requisitos, não dá pra saber - e este costuma ser o limitante de muitas apostilas, pois geralmente elas se focam no mecanismo e não vão além disso (ou seja, não explicam os casos reais, vantagens e desvantagens de cada abordagem, etc). Por exemplo, se a ideia é ter um software de taxonomia, que possua todas as categorias existentes (reino, filo, classe, ordem, etc). Será que vale a pena criar classes e subclasses pra cada coisa? Eu acho que não, pois existem milhões de espécies, divididas em milhares de gêneros, famílias, ordens, etc. Criar uma hierarquia com milhares/milhões de classes é uma insanidade, sem contar que cada vez que uma espécie nova fosse descoberta, vc teria que criar uma nova classe. Neste caso seria melhor ter uma única classe `SerVivo`, que possua os atributos `reino`, `filo`, `classe`, etc. Os reinos podem até ser um `enum`, por exemplo, já que são poucos. Quando existem mais valores possíveis (50? 100?) e que não mudam com tanta frequência, poderia ser um array fixo de strings, e o valor do campo é o índice do array. E quando existem milhares ou milhões de possibilidades, aí o atributo é apenas uma string. E para características muito específicas, poderia ter um `Map` genérico contendo vários pares de "nome/valor". Agora se vc só quer modelar meia dúzia de felinos para um problema pequeno e específico, talvez não faça tanta diferença usar herança ou composição. Mas ainda sim precisaria avaliar os requisitos pra ter certeza.
Acho que utilizando herança onde vai ter a classe mae Felino com todas as carecteristicas que todos tem em comum e as classes filhas Trige, Gato etc. Que herdam as caracteristicas da classe Felino e tem suas proprias caracteristicas.

Bacana seu post, mas acho importante ressaltar que, embora a orientação a objetos tenha sido adotada como o "padrão" pela indústria e boa parte das universidades, é provavelmente o pior paradigma. Esses conceitos, inclusive o exemplo que você deu, ao meu ver fazem muito sentido para o desenvolvimento de jogos, no entanto.