Executando verificação de segurança...
9

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
Carregando publicação patrocinada...
5

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...

1

O importante foi compreender a base que é fundamental para todo o aprendizado seja qual for o conteúdo.

Como você se preocupa em aprender a base (o que te parabenizo, é raro isso) é importante lembrar que você ou está misturando conceitos ou aprendeu errado (fica tranquilo que isso é normal, faz parte e é importante aprender errado de vez enquando)

É como se fosse uma "cópia" do modelo que pode ser manipulado individualmente.

Não é bem assim que acontece, Python meio que não deixa transparecer mas não é uma cópia (tem um paradigma de POO que é cópia, mas não é o caso, veja esse exemplo:

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 descreve_xicara(self):
        return f"Uma xícara {self.cor} feita de {self.material} com capacidade de {self.capacidade} ml."

minha_caneca = Caneca('azul', 'cerâmica', 350)

print(minha_caneca.descrever())

Caneca.descrever = descreve_xicara

print(minha_caneca.descrever())

O que realmente acontece é melhor ilustrado em Lua:

Caneca = {
  new = function (self, cor, material, capacidade)
    local instance = {}
    instance.cor = cor
    instance.material = material
    instance.capacidade = capacidade
    
    -- Diz pra procurar (__index) chaves nulas de 
    -- instance em self (que é Caneca)
    return setmetatable(instance,{__index = self})
  end,
  descrever = function (self)
    return ("Uma xícara %s feita de %s com capacidade de %d ml."):format(self.cor, self.material, self.capacidade)
  end
}

minha_caneca = Caneca:new('azul', 'cerâmica', 350)
minha_caneca:descrever()

O que acontece em descrever:

  • Busca no objeto minha_caneca pelo campo "descrever"
  • Tem? Chama minha_caneca.descrever(minha_caneca)
  • Não tem? Chama Caneca.descrever(minha_caneca)

O que eu quero ilustrar é que uma classe é um objeto de "fallback" ou seja se o que eu procuro não estiver no objeto, eu procuro na classe, quando você instancia uma classe em um objeto, o que você está dizendo pro compilador (sim, python é compilada) é: "Compilador, seguinte, se eu procurar uma propriedade ou método em minha_caneca e não achar, por favor, procure em Caneca porque provavelmente vai estar lá", é por isso que POO com classes geralmente ocupa menos espaço em RAM em troca de ficar lento quando acontece o inheritance hell (heranças em excesso), isso se chama lookup chain

uma classe é um modelo para criar objetos

Eu inverti a ordem pra facilitar entender o conceito, como a gente viu no exemplo anterior, uma classe é um objeto no qual outros objetos podem usar para buscar propriedades e métodos faltantes, um modelo para crar objetos GERALMENTE é uma função, essa técnica é chamada de prototyping (as vezes como fábricas) em Python seria algo assim:

# Importante porque vamos precisar de acesso a namespace do objeto
import types

def Caneca(cor, material, capacidade):
    def descrever(self):
        return f"Uma caneca {self.cor} feita de {self.material} com capacidade de {self.capacidade} ml."
    # Cria um objeto vazio com acesso direto a namespace
    instance = types.SimpleNamespace()
    # Definimos as propriedades
    instance.cor = cor
    instance.material = material
    instance.capacidade = capacidade
    # E os métodos
    instance.descrever = types.MethodType(descrever,instance)
    # E agora é só retornar
    return instance

minha_caneca = Caneca('azul', 'cerâmica', 350)

minha_caneca.descrever() # Uma caneca azul feita de cerâmica com capacidade de 350 ml.
1

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.

2

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.

0

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.

1

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.