Artigo original: How to explain object-oriented programming concepts to a 6-year-old

Você já percebeu como as mesmas questões clichê aparecem sempre nas entrevistas de emprego, repetidamente?

Tenho certeza de que você sabe o que eu quero dizer.

Por exemplo:

Onde você se vê daqui a cinco anos?

ou, pior ainda:

Qual você considera que seja o seu maior ponto fraco?

Ah…dá um tempo. Eu considero responder essa pergunta um grande ponto fraco! Bom, estou fugindo do tópico.

Por mais triviais que essas questões possam ser, elas são importantes por dar pistas a seu respeito: qual o seu posicionamento mental, sua atitude, suas perspectivas.

Ao responder, você deve ter cuidado, pois pode revelar algo que, mais tarde, fará você se arrepender.

Hoje, eu quero falar sobre um tipo semelhante de pergunta no mundo da programação:

Quais são os princípios fundamentais da programação orientada a objetos?

Eu já estive em ambos os lados dessa pergunta. É um dos tópicos sobre os quais se fazem perguntas nas entrevistas com tanta frequência que você não pode se permitir não saber.

Desenvolvedores iniciantes e juniores geralmente precisam respondê-la. É uma maneira fácil de o entrevistador saber três coisas:

  1. O candidato se preparou para essa entrevista?
    Pontos extras se você escutar a resposta de imediato — mostra uma abordagem séria.
  2. O candidato já passou da fase de ver tutoriais?
    Entender os princípios da programação orientada a objetos (ou POO) mostra que você já está além do copiar e colar de tutoriais e já vê a situação de uma perspectiva mais alta.
  3. O entendimento do candidato é profundo ou é raso?
    O nível de competência nesta questão, em geral, é igual ao nível de competência na maioria dos outros pontos. Pode acreditar.

Os quatro princípios da programação orientada a objetos são encapsulamento, abstração, herança e polimorfismo.

Essas palavras podem ter um tom assustador para um desenvolvedor júnior. As explicações complexas e excessivamente longas da Wikipédia às vezes só aumentam a confusão.

É por isso que eu pensei em dar uma explicação simples e breve para cada um desses conceitos. Pode parecer a forma de explicar isso para uma criança, mas eu, de fato, adoraria ouvir essas respostas se eu estivesse entrevistando um candidato.

Encapsulamento

Vamos dizer que temos um programa. Ele tem alguns objetos com pouca diferença na lógica e que se comunicam uns com os outros — de acordo com as regras definidas no programa.

O encapsulamento é obtido quando cada objeto mantém seu estado privado, dentro de uma classe. Outros objetos não têm acesso direto a esse estado. Em vez disso, eles podem apenas chamar uma lista de funções públicas — chamadas de métodos.

Assim, o objeto gerencia seu estado por meio dos métodos — e nenhuma outra classe pode tocá-lo, a menos que seja explicitamente permitido. Se quiser se comunicar com o objeto, você deve usar os métodos fornecidos. No entanto, por padrão, você não pode mudar o estado.

Digamos que estamos construindo um pequeno jogo ao estilo The Sims. Temos pessoas e temos um gato. Eles se comunicam uns com os outros. Queremos aplicar o encapsulamento, assim, encapsulamos a lógica do “gato” em uma classe Cat. Ela pode ter essa aparência:

M4t8zW9U71xeKSlzT2o8WO47mdzrWkNa4rWv
Você pode alimentar o gato, mas não pode alterar diretamente o nível de fome do gato.

Aqui, o “estado” do gato são as variáveis privadas mood, hungry e energy (temperamento, fome e energia, respectivamente). Ele também tem um método privado meow() (miar). Ele pode chamar o método sempre que quiser, as outras classes não podem dizer ao gato quando miar.

O que elas podem fazer é definindo nos métodos públicos sleep(), play() e feed() (dormir, brincar e se alimentar, respectivamente). Cada um deles modifica o estado interno de algum modo e pode invocar meow(). Por isso, o laço entre os métodos de estado privado e os públicos é feito.

Este é o encapsulamento.

Abstração

A abstração pode ser imaginada como a extensão natural do encapsulamento.

No design orientado a objetos, os programas geralmente são muito grandes. E objetos separados se comunicam muito entre si. Assim, manter uma base de código grande assim por anos — e que vai mudando com o passar do tempo — é difícil.

A abstração é um conceito que visa facilitar a resolução desse problema.

Aplicar abstração significa que cada objeto deve expor somente um mecanismo de alto nível para usá-lo.

Esse mecanismo deve ocultar detalhes da implementação interna. Ele deve revelar apenas as operações relevantes para outros objetos.

Imagine uma cafeteira. Ela faz muitas coisas e barulhos esquisitos por baixo dos panos. Tudo o que você tem que fazer, porém, é colocar café e apertar um botão.

De preferência, esse mecanismo deve ser fácil de usar e mudar muito pouco com o passar do tempo. Pense em um conjunto pequeno de métodos públicos que qualquer outra classe pode chamar sem “saber” de fato como funcionam.

Quer outro exemplo de abstração na vida real?
Pensa na forma como usamos nosso telefone:

hiX0NQOcZFShroq-a3FM5pFP2LV4UUI5mLle
Telefones celulares são complexos, mas seu uso é simples.

Você interage com seu telefone usando apenas alguns botões. O que está acontecendo por debaixo dos panos? Você não precisa saber — os detalhes da implementação estão ocultos. Você precisa apenas conhecer um conjunto pequeno de ações.

Mudanças na implementação — como, por exemplo, uma atualização do software — raramente afetam a abstração que você usa.

Herança

OK, vimos agora como o encapsulamento e a abstração podem nos ajudar a desenvolver e manter uma base de código grande.

Mas você sabe qual é outro problema comum no design de POO?

Os objetos em geral são muito semelhantes. Eles compartilham de uma lógica comum. Mas eles não são inteiramente iguais. Argh…

Assim, como reutilizamos a lógica em comum e extraímos a lógica exclusiva para uma classe em separado? Uma maneira de conseguir isso é pela herança.

Isso significa criar uma classe (filha) derivada de outra classe (pai). Dessa forma, estabelecemos uma hierarquia.

A classe filha reutiliza todos os campos e métodos da classe pai (parte comum) e pode implementar seus próprios campos e códigos (parte exclusiva).

Por exemplo:

ZIm7lFjlrKeMWxcH8fqBapNkuSJIxW9-t9yf
Um Professor particular é um tipo de Professor. Um professor qualquer é do tipo Pessoa.

Se nosso programa precisar gerenciar professores privados e públicos, mas também outro tipo de pessoas, como alunos, podemos implementar essa hierarquia de classes.

Desse modo, cada classe adiciona somente o que é necessário para ela, ao mesmo tempo em que reutiliza a lógica comum das classes pai.

Polimorfismo

Chegamos à palavra mais complexa! Polimorfismo significa “muitas formas” em grego.

Já sabemos o poder da herança e já o utilizamos com satisfação. Mas ele traz um problema.

Vamos supor que temos uma classe pai e algumas classes filhas que herdaram dela. Às vezes, queremos usar uma coleção — por exemplo, uma lista — que contenha uma mistura de todas essas classes. Ou que tenhamos um método implementado para a classe pai — mas queremos usá-lo para as filhas, também.

Isso pode ser resolvido com o polimorfismo.

De maneira simples, o polimorfismo traz um modo de usar uma classe da mesma forma que usamos uma classe pai. Assim, não há confusão com relação a misturar os tipos. Porém, cada classe filha mantém seus próprios métodos como estão.

Isso ocorre tipicamente ao definirmos uma interface (pai) para que seja reutilizada. Ela descreve alguns métodos comuns. Então, cada classe filha implementa sua própria versão desses métodos.

Sempre que uma coleção (como uma lista) ou um método espera que uma instância do pai (onde os métodos comuns são descritos), a linguagem se encarrega de avaliar a implementação certa do método em comum — independentemente de qual filha é passada.

Observe o esboço de implementação de figuras geométricas. Elas reutilizam uma interface comum para calcular a área da superfície e o perímetro:

8GySv1U8Kh9nVVyiTqv5cDuWZC7p0uARVeF0
Triângulo, Círculo e Retângulo agora podem ser usados na mesma coleção

Com essas três figuras herdando do seu pai, Figure Interface, você pode criar uma lista que misture triangles, circles e rectangles e tratá-los como se fossem o mesmo tipo de objeto.

Em seguida, se essa lista tentar calcular a superfície de um elemento, o método correto será encontrado e executado. Se o elemento for um triângulo, CalculateSurface() do triângulo será chamado. Se for um círculo — CalculateSurface() do círculo será chamado e assim por diante.

Se você tem uma função que opera com uma figura usando seu parâmetro, não precisa defini-la três vezes — uma vez para o triângulo, uma para o círculo e uma para o retângulo.

Você pode defini-la uma vez e aceitar Figure como o argumento. Não importa se o que você passar for um triângulo, círculo ou retângulo — contanto que eles implementem CalculateParamter(), seu tipo não importa.

Espero que este artigo tenha ajudado. Você pode usar essas mesmas explicações diretamente em entrevistas de emprego.

Se ainda achou isso difícil de entender, fique à vontade para comentar a respeito.

E agora?

Estar preparado para responder a uma das perguntas clássicas mais utilizadas em todos os tempos nas entrevistas é ótimo. Às vezes, porém, você sequer é chamado para entrevistas.

Em seguida, vou me concentrar naquilo que os empregadores desejam ver em um desenvolvedor júnior e em como se destacar do resto da multidão quando estiver procurando emprego.

Fique ligado.

PWiBgy57Ye32At-VBM3qIcWdVJQ01Td-ILKl
Gostou do que leu? Se quiser me dar uma ajuda, pode me pagar um café! :)