Os 4 Pilares da Programação Orientada a Objetos com Exemplos Práticos
Este “artigo” técnico é um complemento a uma publicação que realizei no Linkedin com o intuito de abordar de forma resumida e objetiva os 4 pilares da orientação a objetos sem entrar no mérito dos detalhes de uma abordagem mais aprofundada ou filosófica sobre o tema.
Minha intenção com a publicação do Linkedin foi atingir os desenvolvedores juniores, iniciantes em programação, desenvolvedores que não foram bem em entrevistas técnicas neste tema ou tiveram algum branco (esquecimento) quando confrontado com as perguntas e quem tem dificuldade de entender esses 4 pilares e como aplicá-los na prática. (Link da publicação aqui)
Aviso Importante: Algumas linguagens orientada a objetos ou que oferecem suporte possuem mecanismos próprios que permitem explicitar em código alguns dos pilares do paradigma. Por isso, para ilustrar os exemplos, a linguagem utilizada é o C#.
A exemplo do polimorfismo. Em linguagens como PHP e Javascript ainda não é possível realizar sobrecarga e/ou sobreposição de métodos de forma explicita e simples. Enquanto que no C#, Java e outras linguagens, isso é mais transparente.
De forma alguma o objetivo aqui é afirmar que uma linguagem é melhor que outra por conta disso, mas, vale ressaltar que tal mecanismo facilita o desenvolvimento e o entendimento sobre como aplicar alguns conceitos.
Abstração
É a capacidade de representar o mundo real em código, seja em classes e/ou interfaces e de criar um conjunto de rotinas capazes de serem reutilizadas para complementar outras, com seus detalhes de implementação ocultos de quem vai usar.
Envolve a implementação da lógica necessária para execução do código. Só que de forma “oculta” de quem usa. Pois quem usa, só precisa saber o que as classes/interfaces fazem, não como fazem. Este pilar é também considerado uma extensão do Encapsulamento.
Exemplo Prático: Representando um Desenvolvedor do mundo real em código.
Note que no exemplo a seguir a classe abstrata “Desenvolvedor” é uma representação do desenvolvedor no mundo real. Devido a isso ela possui nome, linguagem de programação preferida e anos de experiência como características (atributos) e codar, escrever testes unitários, escrever código, verificar tempo de experiência, beber café e etc… como comportamentos (métodos) que ele possui.
Ao usar a classe Desenvolvedor como um objeto precisamos apenas saber o que ela faz ou pode fazer e não precisamos nos preocupar com o como ela faz.
Somente ela conhece e lida com a complexidade de seus métodos.
E no mundo real, só o desenvolvedor sabe lidar com a complexidade de seus comportamentos também.
Além disso, é possível que outra abstração seja criada utilizando essa como “base” e complemento.
Resumindo, se essa classe fosse instanciada (caso não fosse abstrata, é claro) só precisariamos nos preocupar com os métodos que ela oferece para usar. E não como o processo de execução do método é feito.
Aviso Importante: Abstração não consiste necessariamente em criar uma classe abstrata. A classe “Desenvolvedor” foi criada como abstrata somente para fins didáticos e para ser reaproveitada na explicação dos outros pilares.
Encapsulamento
Mecanismo usado para esconder atributos e detalhes de implementação dos dados passados para a instância da classe. Garantindo que o acesso a dados ocorra apenas através de métodos públicos, impedindo que eles sejam alterados em tempo de execução de fora da classe.
Sabe o que é interessante no encapsulamento? É que ele é uma extensão da abstração! Por que?
Porque quando aplicado ele garante que só a própria classe conheça os detalhes de implementação e disponibilize apenas o que é possível fazer com os dados da classe como dito na definição anteriormente.
E como que aplica o encapsulamento na prática mesmo? Simples: Tornando os atributos privados ou protegidos através dos modificadores de acesso ou visibilidade “private” e “protected” e criando métodos que retornem estes atributos apenas.
A seguir, Note que no exemplo da abstração os atributos eram públicos e tinha um setters. Ou seja, podiam ser modificados na instância por qualquer pessoa que utilizasse a classe “Desenvolvedor” e a qualquer momento.
Aviso Importante: O encapsulamento da classe do exemplo a seguir ainda poderá ser “violado” somente pelas classes derivadas (filhas) através de herança, pois a visibilidade está definida como protegida (protected), não como privada (private).
Herança
É uma das formas de relacionar classes/objetos e/ou compartilhar lógica de implementação.
Comumente utilizada para permitir o reuso das características (atributos) e comportamentos (métodos ) que são comuns para coisas que tem algum tipo de parentesco.
Esse tipo relação é comumente expressada como “classe pai e classe filha”, “Super Classe e Subclasse”, “Super Classe e Classe Derivada” e também como “é um(a) alguma coisa”.
A seguir, o exemplo mostra 2 classes derivadas da classe “Desenvolvedor”, são elas “DesenvolvedorCSharp” e “DesenvolvedorJava”. Observe que no mundo real ambos os desenvolvedores tem características e desenvolvem comportamentos similares. Só que cada um de sua forma.
Veja que as características (atributos) e comportamentos (métodos) comuns entre ambos não foram específicados em sua totalidade novamente. Pois, foi tirado proveito do benefício da herança! (Veja que a classe contém métodos abstratos)
Sobre os métodos definidos como abstratos na classe Desenvolvedor, estamos dizendo para as classes que serão derivadas (filhas) dela, que obrigatoriamente elas devem ter aqueles métodos, mas a forma como a lógica deles é executada pode ser diferente uma da outra. Ou seja, eles fazem a mesma coisa de formas diferente.
Aviso Importante: A herança não é a única forma de criar relacionamento entre classes. Existem outras formas como Associação, Composição e Agregação. Mas aqui, apenas a herança será abordada. Pois ela é considerada um dos pilares da orientação a objetos, as outras são consideradas técnicas.
Polimorfismo
É o mecanismo que permite que duas ou mais classes que herdam comportamentos (métodos) através de herança, se comportem de forma diferente.
Ou seja, métodos com o mesmo nome podem executar códigos e lógicas opostas, parecidas, complementares ou completamente diferentes mesmo!
E isso pode ser feito de duas formas: Estática ou Dinâmica.
Polimorfismo na forma estática (Sobrecarga — Overload)
O polimorfismo na forma estática é também muito conhecido como “sobrecarga” ou para os mais íntimos como “overload”.
Isso permite a existência de vários métodos com o mesmo nome mas com quantidade, tipos e/ou ordem de parâmetros levemente diferentes.
Para aplicá-lo é necessário apenas criar outro método com o mesmo nome e uma quantidade de parâmetros diferentes, ordens diferentes ou tipos diferentes (ou não).
Polimorfismo na forma dinâmica(Sobreposição, Reescrita — Override)
Já o polimorfismo na forma forma dinâmica é também muito conhecido como “sobreposição”, “reescrita” ou para os mais íntimos “override”.
Diferente da sobrecarga (forma estática), na sobreposição é necessário que os métodos tenham exatamente o mesmo nome, tipo de retorno e quantidade de parâmetros.
Para aplicá-lo, basta criar outro método com o mesmo nome, tipo e retorno, não precisa necessariamente ter parâmetros. A implementação de sua lógica que deve ser diferente. Inclusive, ela pode ser complementada com o método da classe base (pai/herdada) caso ele já tenha implementação (Olha só como abstração pode ser complementada, lembra?)
Note que no exemplo a seguir as duas formas de polimorfismo são aplicadas. tanto a sobrecarga como a sobreposição.
Aviso Importante: Por se tratar de um exemplo com objetivos didáticos, a implementação foi feita de forma bem sútil.
Em um projeto real as diferenças podem ser gritantes. Além disso, no exemplo da sobrecarga apenas a ordem dos parâmetros foi alterada.
E no exemplo da sobrescrita apenas os valores de comparação foram alterados e foi feita a inclusão de uma verificação adicional.
Note a presença das palavras-chave
virtual
eoverride
que no C# tratam-se de um recurso que nos permite sinalizar e identificar métodos que sofrem aplicação do polimorfismo na forma dinâmica. Onde “virtual” indica que o método da classe base pode ser sobrescrito por um método da classe deriva e essa sobrescrita é feita através do uso da palavra-chave “override” .
Conclusão
Acredito que agora você tem uma ideia melhor sobre os 4 pilares básicos da orientação a objetos e poderá explicá-lo para os outros de forma simples. Seja no dia a dia de trabalho, na faculdade ou até mesmo na entrevista técnica!!! Além disso saberá também como aplicar na prática!!! Olha que top?!!
Mas lembre-se sempre de que estes pilares sempre estarão na vanguarda de qualquer linguagem de programação orientada a objetos.
Eles podem até parecer complicados no começo, mas quando você entende de fato qual o papel de cada um deles você só colhe benefícios, especialmente quando sabe aplicar bem na prática.
E eles existem neste paradigma para facilitar nossa compreensão sobre concepção, segurança, relações e comportamentos de classes e objetos em qualquer linguagem de programação que ofereça suporte nativamente ou não.
Se você gostou desse artigo, deixa seu “gostei” e compartilha com algum amigo ou colega de trabalho. E se tiver alguma crítica, complemento ou sugestão de correção de algum “BUG” detectado no artigo, deixa nos comentários ou entra em contato comigo através do linkedin.
Será um prazer ouvi-lo(a)!!!!
Referências
- WEISFELD, Matt. The Object-Oriented Thought Process. 5. ed. San Francisco, Ca: Addison-Wesley Professional, 2019.
- CARDELLI, L.; WEGNER, P. On Understanding Types, Data Abstraction, and Polymorphism. ACM Computing Surveys (CSUR). vol.17, pag. 471–524. 1985.