You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
usingSystem;abstractclassDesenvolvedor{publicstringNome{get;set;}publicLinguagemDeProgramacaoLinguagemPreferida{get;set;}publicintAnosDeExperiencia{get;set;}protectedDesenvolvedor(stringnome,intanosDeExperiencia,LinguagemDeProgramacaolinguagemPreferida){Nome=nome;LinguagemPreferida=linguagemPreferida;AnosDeExperiencia=anosDeExperiencia;}publicabstractvoidCodar(stringcodigo);publicabstractvoidEscreverTestesUnitarios(stringcodigo);publicboolEhRaiz(){returnAnosDeExperiencia>=10;}publicvoidBeberCafe(){PegarCaneca();EscolherCafe();EncherCaneca();EntornarNaBoca();}protectedvoidEscreverCodigo(stringcodigo){Console.WriteLine(codigo+" - Desenvolvidor por "+Nome);}protectedvoidMapearCenariosDeTestes(){// Implementação...}privatevoidPegarCaneca(){// Implementação...}privatevoidEscolherCafe(){// Implementação...}privatevoidEncherCaneca(){// Implementação...}privatevoidEntornarNaBoca();{// Implementação...}}
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).
usingSystem;abstractclassDesenvolvedor{/** * 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 utilize a classe. */protectedstringNome{get;}protectedLinguagemDeProgramacaoLinguagemPreferida{get;}protectedintAnosDeExperiencia{get;}protectedDesenvolvedor(stringnome,intanosDeExperiencia,LinguagemDeProgramacaolinguagemPreferida){Nome=nome;LinguagemPreferida=linguagemPreferida;AnosDeExperiencia=anosDeExperiencia;}publicabstractvoidCodar(stringcodigo);publicabstractvoidEscreverTestesUnitarios(stringcodigo);/** * Sem encapsulamento a mesma instância dessa classe poderia ter seu valor modificado * diversas vezes. E isso é uma brecha de segurança para os dados e a execução da implementação. * * Pois perde-se a garantia da integridade do objeto e de seus dados. * * No caso deste método os anos de experiência do desenvolvedor não seriam consistentes * e nem a verificação de que ele é "raiz", pois essa informação estaria mudando a gosto do freguês. * * Quando mudar dessa forma, não é o objetivo deste exemplo. * * Com o encapsulamento só é possível consultar o valor e ele será único conforme a instância * é gerada. E só poderá ser modificado pela própria classe. */publicboolEhRaiz(){returnAnosDeExperiencia>=10;}publicvoidBeberCafe(){PegarCaneca();EscolherCafe();EncherCaneca();EntornarNaBoca();}protectedvoidEscreverCodigo(stringcodigo){Console.WriteLine(codigo+" - Desenvolvidor por "+Nome);}protectedvoidMapearCenariosDeTestes(){// Implementação...}privatevoidPegarCaneca(){// Implementação...}privatevoidEscolherCafe(){// Implementação...}privatevoidEncherCaneca(){// Implementação...}privatevoidEntornarNaBoca();{// Implementação...}}
usingSystem;classProgram{publicstaticvoidMain(){/** * Exemplo Violando Encapsulamento * * Obs: Esse código só seria executado se a classe não fosse abstrata e se o atributo "AnosDeExperiencia" ainda fosse público * Mas para fins de exemplo, essa demonstração é válida */DesenvolvedordesenvolvedorJava=newDesenvolvedor("Javeiro",10,LinguagemDeProgramacao.Java);desenvolvedorJava.AnosDeExperiencia=5;if(desenvolvedorJava.EhRaiz()){desenvolvedorJava.BeberCafe();}desenvolvedorJava.AnosDeExperiencia=12;/** * Exemplo Aplicando Encapsulamento * * Não é possível alterar os anos de experiência agora, pois ele não está acessível. A instância criada * não o expõe. O mundo externo ou quem está usando a classe não conhece os atributos. Pois eles estão protegidos. * */DesenvolvedordesenvolvedorCSharp=newDesenvolvedor("Desenvolvedor CSharp",10,LinguagemDeProgramacao.CSharp);// Um erro ocorre aqui ao tentar acessar um atributo desconhecido externamentedesenvolvedorCSharp.AnosDeExperiencia=5;// Temos segurança de que esse método funcionará corretamente com os dados iniciais passados para instânciaif(desenvolvedorCSharp.EhRaiz()){desenvolvedorCSharp.BeberCafe();}}}
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.
usingSystem;publicclassDesenvolvedorCSharp:Desenvolvedor{publicDesenvolvedorCSharp(stringnome,intanosDeExperiencia):base(nome,anosDeExperiencia,LinguagemDeProgramacao.CSharp){Nome=nome;AnosDeExperiencia=anosDeExperiencia;}/** * O Desenvolvedor C# tem sua forma de codar. * Portanto, o método codar foi definido como abstrato na classe base (Desenvolvedor) * Assim, é possível implementar de forma que se enquadre ao contexto da classe derivada (filha) */publicvoidCodar(stringcodigo){AbrirVisualStudioIDE();ConfigurarAppSettings();CriarSolucao();CriarProjetos();ReferenciarProjetos();EscreverCodigo(codigo);}publicvoidEscreverTestesUnitarios(stringcodigo){MapearCenariosDeTestes();EscreverCodigo(codigo);}privatevoidAbrirVisualStudioIDE(){// Implementação...}privatevoidConfigurarAppSettings(){// Implementação...}privatevoidCriarSolucao(){// Implementação...}privatevoidCriarProjetos(){// Implementação...}privatevoidReferenciarProjetos(){// Implementação...}}
usingSystem;publicclassDesenvolvedorJava:Desenvolvedor{publicDesenvolvedorJava(stringnome,intanosDeExperiencia):base(nome,anosDeExperiencia,LinguagemDeProgramacao.Java){Nome=nome;AnosDeExperiencia=anosDeExperiencia;}/** * O Desenvolvedor Java tem sua forma de codar. * Portanto, o método codar foi definido como abstrato na classe base (Desenvolvedor) * Assim, é possível implementar de forma que se enquadre ao contexto da classe derivada (filha) * Note a diferença entre a implementação deste método e a do método da classe DesenvolvedorCSharp */publicvoidCodar(stringcodigo){AbrirEclipseIDE();ConfigurarPomXml();ResolverProblemasDaIDE();ResolverProblemasDoMaven();EsperarBoaVontadeDaIDEAbrir();EscreverCodigo(codigo);}/** * O mesmo ocorre com este método. * Observe que também não é necessário criar uma implementação parecida em algum aspecto com * o de outras classes derivadas. Na classe DesenvolvedorCSharp a quantidade de métodos auxiliares * na implementação é menor, aqui alguns passos a mais são executados. */publicvoidEscreverTestesUnitarios(stringcodigo){MapearCenariosDeTestes();ConfigurarPomXml();ConfigurarJUnit();EscreverCodigo(codigo);}privatevoidAbrirEclipseIDE(){// Implementação...}privatevoidConfigurarPomXml(){// Implementação...}privatevoidResolverProblemasDaIDE(){// Implementação...}privatevoidResolverProblemasDoMaven(){// Implementação...}privatevoidEsperarBoaVontadeDaIDEAbrir(){// Implementação...}}
usingSystem;classProgram{publicstaticvoidMain(){// Os parâmetros definidos no construtor da classe base (Desenvolvedor) são passados normalmente para as classes derivadas (filhas)// Ou seja, as classes DesenvolvedorCSharp e DesenvolvedorJava também possuem os atributos Nome, AnosDeExperiencia e LinguagemDeProgramacao// Pois herdaram estes foram herdadosDesenvolvedorCSharpdesenvolvedorCSharp=newDesenvolvedorCSharp("Desenvolvedor CSharp",10);DesenvolvedorJavadesenvolvedorJava=newDesenvolvedorJava("Javeiro",12);// Note que, assim como os atributos, os métodos também foram herdadosdesenvolvedorCSharp.Codar("Olá Mundo");desenvolvedorCSharp.EscreverTestesUnitarios("Teste Com CSharp - Olá Mundo");desenvolvedorJava.Codar("Olá Mundo");desenvolvedorJava.EscreverTestesUnitarios("Teste com Java - Olá Mundo");}}
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 virtuale overrideque 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" .
usingSystem;abstractclassDesenvolvedor{protectedstringNome{get;}protectedLinguagemDeProgramacaoLinguagemPreferida{get;}protectedintAnosDeExperiencia{get;}protectedDesenvolvedor(stringnome,intanosDeExperiencia,LinguagemDeProgramacaolinguagemPreferida){Nome=nome;LinguagemPreferida=linguagemPreferida;AnosDeExperiencia=anosDeExperiencia;}publicabstractvoidCodar(stringcodigo);publicabstractvoidEscreverTestesUnitarios(stringcodigo);publicvirtualboolEhRaiz(){returnAnosDeExperiencia>=10;}publicvoidBeberCafe(){PegarCaneca();EscolherCafe();EncherCaneca();EntornarNaBoca();}protectedvoidEscreverCodigo(stringcodigo){Console.WriteLine(codigo+" - Desenvolvidor por "+Nome);}protectedvoidMapearCenariosDeTestes(){// Implementação...}privatevoidPegarCaneca(){// Implementação...}privatevoidEscolherCafe(){// Implementação...}privatevoidEncherCaneca(){// Implementação...}privatevoidEntornarNaBoca();{// Implementação...}}
usingSystem;publicclassDesenvolvedorCSharp:Desenvolvedor{publicDesenvolvedorCSharp(stringnome,intanosDeExperiencia):base(nome,anosDeExperiencia,LinguagemDeProgramacao.CSharp){Nome=nome;AnosDeExperiencia=anosDeExperiencia;}// Olha a sobrecarga!!! Neste caso os parâmetros só estão sendo passados em ordens diferentespublicDesenvolvedorCSharp(intanosDeExperiencia,stringnome):base(nome,anosDeExperiencia,LinguagemDeProgramacao.CSharp){AnosDeExperiencia=anosDeExperiencia;Nome=nome;}/** * Olha a sobrescrita (override). * Aqui apenas o valor usado para comparação é diferente, mas é uma variação na implementação. */publicoverrideEhRaiz(){returnAnosDeExperiencia>=30;}publicvoidCodar(stringcodigo){AbrirVisualStudioIDE();ConfigurarAppSettings();CriarSolucao();CriarProjetos();ReferenciarProjetos();EscreverCodigo(codigo);}publicvoidEscreverTestesUnitarios(stringcodigo){MapearCenariosDeTestes();EscreverCodigo(codigo);}privatevoidAbrirVisualStudioIDE(){// Implementação...}privatevoidConfigurarAppSettings(){// Implementação...}privatevoidCriarSolucao(){// Implementação...}privatevoidCriarProjetos(){// Implementação...}privatevoidReferenciarProjetos(){// Implementação...}}
usingSystem;publicclassDesenvolvedorJava:Desenvolvedor{publicDesenvolvedorJava(stringnome,intanosDeExperiencia):base(nome,anosDeExperiencia,LinguagemDeProgramacao.Java){Nome=nome;AnosDeExperiencia=anosDeExperiencia;}// Olha a sobrecarga!!! Neste caso os parâmetros só estão sendo passados em ordens diferentespublicDesenvolvedorJava(intanosDeExperiencia,stringnome):base(nome,anosDeExperiencia,LinguagemDeProgramacao.Java){AnosDeExperiencia=anosDeExperiencia;Nome=nome;}/** * Olha a sobrescrita (override). * Diferente da classe DesenvolvedorCSharp, aqui além do valor usado para comparação ser diferente, * uma verificação nom atributo nome é feita. * * Ou seja, este método, nessa classe verifica se o desenvolvedor é raiz de forma diferente! */publicoverrideEhRaiz(){returnNome.Contains("Dinossauro")&&AnosDeExperiencia>=60;}publicvoidCodar(stringcodigo){AbrirEclipseIDE();ConfigurarPomXml();ResolverProblemasDaIDE();ResolverProblemasDoMaven();EsperarBoaVontadeDaIDEAbrir();EscreverCodigo(codigo);}publicvoidEscreverTestesUnitarios(stringcodigo){MapearCenariosDeTestes();ConfigurarPomXml();ConfigurarJUnit();EscreverCodigo(codigo);}privatevoidAbrirEclipseIDE(){// Implementação...}privatevoidConfigurarPomXml(){// Implementação...}privatevoidResolverProblemasDaIDE(){// Implementação...}privatevoidResolverProblemasDoMaven(){// Implementação...}privatevoidEsperarBoaVontadeDaIDEAbrir(){// Implementação...}}
usingSystem;classProgram{publicstaticvoidMain(){/** * Note que os parâmetros do construtor de cada classe são passados em ordens diferentes * É nesse cenário que ocorre a sobrecarga do método */DesenvolvedordesenvolvedorCSharp=newDesenvolvedorCSharp("Desenvolvedor CSharp",31);DesenvolvedoroutroDesenvolvedorCSharp=newDesenvolvedorCSharp(8,"Outro Desenvolvedor CSharp");DesenvolvedordesenvolvedorJava=newDesenvolvedorJava(65,"Javeiro Dinossaro");DesenvolvedoroutroDesenvolvedorJava=newDesenvolvedorJava("Javeiro",6);desenvolvedorCSharp.Codar("Olá Mundo");outroDesenvolvedorCSharp.Codar("Outro Olá Mundo");desenvolvedorCSharp.EscreverTestesUnitarios("Teste Com CSharp - Olá Mundo");outroDesenvolvedorCSharp.EscreverTestesUnitarios("Outro Teste Com CSharp - Olá Mundo");desenvolvedorJava.Codar("Olá Mundo");outroDesenvolvedorJava.Codar("Outro Olá Mundo");desenvolvedorJava.EscreverTestesUnitarios("Teste com Java - Olá Mundo");outroDesenvolvedorJava.EscreverTestesUnitarios("Outro Teste com Java - Olá Mundo");/** * Note que só beberá o café os desenvolvedores que atenderem as condições especificadas na implementação de seus métodos * que verificam se eles são "raiz" ou não. Ou seja, o resultado varia conforme a lógica, mas, no final faz a mesma coisa. */if(desenvolvedorCSharp.EhRaiz()){desenvolvedorCSharp.BeberCafe();}if(outroDesenvolvedorCSharp.EhRaiz()){outroDesenvolvedorCSharp.BeberCafe();}if(desenvolvedorJava.EhRaiz()){desenvolvedorJava.BeberCafe();}if(outroDesenvolvedorJava.EhRaiz()){outroDesenvolvedorJava.BeberCafe();}}}
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.
Kaique Prazeres é Arquiteto de Soluções na Operação Nacional da Programmers Beyond IT e é um ferrenho defensor de boas práticas e qualidade de software com foco em redução de custos e aumento de longevidade de sistemas.
The text was updated successfully, but these errors were encountered:
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)
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.
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.
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.
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.
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
Sobre o Autor
Kaique Prazeres é Arquiteto de Soluções na Operação Nacional da Programmers Beyond IT e é um ferrenho defensor de boas práticas e qualidade de software com foco em redução de custos e aumento de longevidade de sistemas.
The text was updated successfully, but these errors were encountered: