From 08a013da54806e04bc76388ef0ef129259bbc4de Mon Sep 17 00:00:00 2001 From: Eduardo Klosowski Date: Mon, 26 Apr 2021 16:01:54 -0300 Subject: [PATCH 1/2] =?UTF-8?q?Adiciona=20artigo:=20Orienta=C3=A7=C3=A3o?= =?UTF-8?q?=20a=20objetos=20de=20outra=20forma:=20Heran=C3=A7a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/oo-de-outra-forma-3.md | 225 +++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 content/oo-de-outra-forma-3.md diff --git a/content/oo-de-outra-forma-3.md b/content/oo-de-outra-forma-3.md new file mode 100644 index 000000000..0b1d55ace --- /dev/null +++ b/content/oo-de-outra-forma-3.md @@ -0,0 +1,225 @@ +Title: Orientação a objetos de outra forma: Herança +Slug: oo-de-outra-forma-3 +Date: 2021-04-26 17:00 +Category: Python +Tags: python, orientação a objetos +Author: Eduardo Klosowski +Email: eduardo_klosowski@yahoo.com +Github: eduardoklosowski +Twitter: eduklosowski +Site: https://dev.to/eduardoklosowski +About_author: Programador, formado em redes de computadores e estuda DevOps + +Algo que ajuda no desenvolvimento é a reutilização de código. Em orientação a objetos, essa reutilização pode ocorrer através de herança, onde um objeto pode se comportar como um objeto da sua própria classe, como também da classe que herdou. + +## Adicionando funcionalidades + +Uma das utilidades da herança é estender uma classe para adicionar funcionalidades. Pensando no contexto das postagens anteriores, poderíamos querer criar um usuário e senha para algumas pessoas poderem acessar o sistema. Isso poderia ser feito adicionando atributos usuário e senha para as pessoas, além de uma função para validar se os dados estão corretos, e assim permitir o acesso ao sistema. Porém isso não pode ser feito para todas as pessoas, e sim apenas para aqueles que possuem permissão de acesso. + +### Sem orientação a objetos + +Voltando a solução com dicionários (sem utilizar orientação a objetos), isso consistiria em criar um dicionário com a estrutura de uma pessoa, e em seguida estender essa estrutura com os novos campos de usuário e senha nesse mesmo dicionário, algo como: + +```python +# Arquivo: pessoa.py + +def init(pessoa, nome, sobrenome, idade): + pessoa['nome'] = nome + pessoa['sobrenome'] = sobrenome + pessoa['idade'] = idade + + +def nome_completo(pessoa): + return f"{pessoa['nome']} {pessoa['sobrenome']}" +``` + +```python +# Arquivo: pessoa_autenticavel.py + +def init(pessoa, usuario, senha): + pessoa['usuario'] = usuario + pessoa['senha'] = senha + + +def autenticar(pessoa, usuario, senha): + return pessoa['usuario'] == usuario and pessoa['senha'] == senha +``` + +```python +import pessoa +import pessoa_autenticavel + +p = {} +pessoa.init(p, 'João', 'da Silva', 20) +pessoa_autenticavel.init(p, 'joao', 'secreta') + +print(pessoa.nome_completo(p)) +print(pessoa_autenticavel.autenticar(p, 'joao', 'secreta')) +``` + +Porém nessa solução é possível que o programador esqueça de chamar as duas funções `init` diferentes, e como queremos que todo dicionário com a estrutura de `pessoa_autenticavel` contenha também a estrutura de `pessoa`, podemos chamar o `init` de pessoa dentro do `init` de `pessoa_autenticavel`: + +```python +# Arquivo: pessoa_autenticavel.py + +import pessoa + + +def init(p, nome, sobrenome, idade, usuario, senha): + pessoa.init(p, nome, sobrenome, idade) + p['usuario'] = usuario + p['senha'] = senha + + +... # Demais funções +``` + +```python +import pessoa +import pessoa_autenticavel + +p = {} +pessoa_autenticavel.init(p, 'João', 'da Silva', 20, 'joao', 'secreta') + +print(pessoa.nome_completo(p)) +print(pessoa_autenticavel.autenticar(p, 'joao', 'secreta')) +``` + +Nesse caso foi necessário alterar o nome do argumento `pessoa` da função `pessoa_autenticavel.init` para não conflitar com o outro módulo importado com esse mesmo nome. Porém ao chamar um `init` dentro de outro, temos a garantia de que o dicionário será compatível tanto com a estrutura pedida para ser criada pelo programador, quanto pelas estruturas pais dela. + +### Com orientação a objetos + +```python +class Pessoa: + def __init__(self, nome, sobrenome, idade): + self.nome = nome + self.sobrenome = sobrenome + self.idade = idade + + def nome_completo(self): + return f'{self.nome} {self.sobrenome}' + + +class PessoaAutenticavel(Pessoa): + def __init__(self, nome, sobrenome, idade, usuario, senha): + Pessoa.__init__(self, nome, sobrenome, idade) + self.usuario = usuario + self.senha = senha + + def autenticar(self, usuario, senha): + return self.usuario == usuario and self.senha == senha + + +p = PessoaAutenticavel('João', 'da Silva', 20, 'joao', 'secreta') + +print(Pessoa.nome_completo(p)) +print(PessoaAutenticavel.autenticar(p, 'joao', 'secreta')) +``` + +A principal novidade desse exemplo é que ao declarar a classe `PessoaAutenticavel` (filha), foi declarado a classe `Pessoa` (pai) entre parênteses, isso faz o interpretador Python criar uma cópia dessa classe estendendo-a com as novas funções que estamos criando. Porém pode ser um pouco redundante chamar `Pessoa.__init__` dentro da função `__init__` sendo que já foi declarado que ela estende `Pessoa`, podendo ser trocado por `super()`, que aponta para a classe que foi estendida. Exemplo: + +```python +class PessoaAutenticavel(Pessoa): + def __init__(self, nome, sobrenome, idade, usuario, senha): + super().__init__(nome, sobrenome, idade) + self.usuario = usuario + self.senha = senha + + ... # Demais funções +``` + +Assim se evita repetir o nome da classe, e já passa automaticamente a referência para `self`, assim como quando usamos o açúcar sintático apresentado na primeira postagem dessa série. E esse açúcar sintática também pode ser usado para chamar tanto as funções declaradas em `Pessoa` quanto em `PessoaAutenticavel`. Exemplo: + +```python +p = PessoaAutenticavel('João', 'da Silva', 20, 'joao', 'secreta') + +print(p.nome_completo()) +print(p.autenticar('joao', 'secreta')) +``` + +Esse método também facilita a utilização das funções, uma vez que não é necessário lembrar em qual classe que cada função foi declarada. Na verdade, como `PessoaAutenticavel` estende `Pessoa`, seria possível executar também `PessoaAutenticavel.nome_completo`, porém eles apontam para a mesma função. + +## Sobrescrevendo uma função + +A classe `Pessoa` possui a função `nome_completo` que retorna uma `str` contento nome e sobrenome. Porém no Japão, assim como em outros países asiáticos, o sobrenome vem primeiro, e até [estão pedindo para seguir a tradição deles ao falarem os nomes de japoneses](https://noticias.uol.com.br/ultimas-noticias/efe/2019/06/24/japao-quer-voltar-a-ordem-tradicional-dos-nomes-abe-shinzo-nao-shinzo-abe.htm), como o caso do primeiro-ministro, mudando de Shinzo Abe para Abe Shinzo. + +### Com orientação a objetos + +Isso também pode ser feito no sistema usando herança, porém em vez de criar uma nova função com outro nome, é possível criar uma função com o mesmo nome, sobrescrevendo a anterior, porém apenas para os objetos da classe filha. Algo semelhante ao que já foi feito com a função `__init__`. Exemplo: + +```python +class Japones(Pessoa): + def nome_completo(self): + return f'{self.sobrenome} {self.nome}' + + +p1 = Pessoa('João', 'da Silva', 20) +p2 = Japones('Shinzo', 'Abe', 66) + +print(p1.nome_completo()) # João da Silva +print(p2.nome_completo()) # Abe Shinzo +``` + +Essa relação de herança traz algo interessante, todo objeto da classe `Japones` se comporta como um objeto da classe `Pessoa`, porém a relação inversa não é verdade. Assim como podemos dizer que todo japonês é uma pessoa, mas nem todas as pessoas são japonesas. Ser japonês é um caso mais específico de pessoa, assim como as demais nacionalidades. + +### Sem orientação a objetos + +Esse comportamento de sobrescrever a função `nome_completo` não é tão simples de replicar em uma estrutura de dicionário, porém é possível fazer. Porém como uma pessoa pode ser tanto japonês quanto não ser, não é possível saber de antemão para escrever no código `pessoa.nome_completo` ou `japones.nome_completo`, que diferente do exemplo da autenticação, agora são duas funções diferentes, isso precisa ser descoberto dinamicamente quando se precisar chamar a função. + +Uma forma de fazer isso é guardar uma referência para a função que deve ser chamada dentro da própria estrutura. Exemplo: + +```python +# Arquivo: pessoa.py + +def init(pessoa, nome, sobrenome, idade): + pessoa['nome'] = nome + pessoa['sobrenome'] = sobrenome + pessoa['idade'] = idade + pessoa['nome_completo'] = nome_completo + + +def nome_completo(pessoa): + return f"{pessoa['nome']} {pessoa['sobrenome']}" +``` + +```python +# Arquivo: japones.py + +import pessoa + + +def init(japones, nome, sobrenome, idade): + pessoa(japones, nome, sobrenome, idade) + japones['nome_completo'] = nome_completo + + +def nome_completo(japones): + return f"{pessoa['sobrenome']} {pessoa['nome']}" +``` + +```python +import pessoa +import japones + +p1 = {} +pessoa.init(p1, 'João', 'da Silva', 20) +p2 = {} +japones.init(p2, 'Shinzo', 'Abe', 66) + +print(p1['nome_completo'](p1)) # João da Silva +print(p2['nome_completo'](p2)) # Abe Shinzo +``` + +Perceba que a forma de chamar a função foi alterada. O que acontece na prática é que toda função que pode ser sobrescrita não é chamada diretamente, e sim a partir de uma referência, e isso gera um custo computacional adicional. Como esse custo não é tão alto (muitas vezes sendo quase irrelevante), esse é o comportamento adotado em várias linguagens, porém em C++, por exemplo, existe a palavra-chave `virtual` para descrever quando uma função pode ser sobrescrita ou não. + +## Considerações + +Herança é um mecanismo interessante para ser explorado com o objetivo de reaproveitar código e evitar repeti-lo. Porém isso pode vir com alguns custos, seja computacional durante sua execução, seja durante a leitura do código, sendo necessário verificar diversas classes para saber o que de fato está sendo executado, porém isso também pode ser usado para ocultar e abstrair lógicas mais complicadas, como eu já comentei em outra [postagem](https://dev.to/acaverna/encapsulamento-da-logica-do-algoritmo-298e). + +Herança também permite trabalhar com generalização e especialização, podendo descrever o comportamento mais geral, ou mais específico. Ou simplesmente só adicionar mais funcionalidades a uma classe já existente. + +Assim como foi utilizado o `super()` para chamar a função `__init__` da classe pai, é possível utilizá-lo para chamar qualquer outra função. Isso permite, por exemplo, tratar os argumentos da função, aplicando modificações antes de chamar a função original, ou seu retorno, executando algum processamento em cima do retorno dela, não precisando rescrever toda a função. + +--- + +Esse artigo foi publicado originalmente no [meu blog](https://eduardoklosowski.github.io/blog/), passe por lá, ou siga-me no [DEV](https://dev.to/eduardoklosowski) para ver mais artigos que eu escrevi. From 154cb3e0b5004fbc34eb03c46e26f0c683f2e517 Mon Sep 17 00:00:00 2001 From: Eduardo Klosowski Date: Mon, 3 May 2021 13:38:13 -0300 Subject: [PATCH 2/2] =?UTF-8?q?Adiciona=20artigo:=20Orienta=C3=A7=C3=A3o?= =?UTF-8?q?=20a=20objetos=20de=20outra=20forma:=20Heran=C3=A7a=20m=C3=BAlt?= =?UTF-8?q?iplas=20e=20mixins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oo-de-outra-forma-4/mro.png | Bin 0 -> 5799 bytes content/oo-de-outra-forma-4.md | 161 ++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 content/images/eduardoklosowski/oo-de-outra-forma-4/mro.png create mode 100644 content/oo-de-outra-forma-4.md diff --git a/content/images/eduardoklosowski/oo-de-outra-forma-4/mro.png b/content/images/eduardoklosowski/oo-de-outra-forma-4/mro.png new file mode 100644 index 0000000000000000000000000000000000000000..9a126c665efb15afc9c180f17c364a2de4c54850 GIT binary patch literal 5799 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 M!gV!D)ow@p7aL_9U;qFB literal 0 HcmV?d00001 diff --git a/content/oo-de-outra-forma-4.md b/content/oo-de-outra-forma-4.md new file mode 100644 index 000000000..d458650f3 --- /dev/null +++ b/content/oo-de-outra-forma-4.md @@ -0,0 +1,161 @@ +Title: Orientação a objetos de outra forma: Herança múltiplas e mixins +Slug: oo-de-outra-forma-4 +Date: 2021-05-03 15:00 +Category: Python +Tags: python, orientação a objetos +Author: Eduardo Klosowski +Email: eduardo_klosowski@yahoo.com +Github: eduardoklosowski +Twitter: eduklosowski +Site: https://dev.to/eduardoklosowski +About_author: Programador, formado em redes de computadores e estuda DevOps + +No [texto anterior](https://dev.to/acaverna/orientacao-a-objetos-de-outra-forma-heranca-3dm7) foi apresentando o conceito de herança, que herda toda a estrutura e comportamento de uma classe, podendo estendê-la com outros atributos e comportamentos. Esse texto apresentará a ideia de [herança múltipla](https://pt.wikipedia.org/wiki/Heran%C3%A7a_m%C3%BAltipla), e uma forma para se aproveitar esse recurso, através de mixins. + +## Herança múltiplas + +Voltando ao sistema para lidar com dados das pessoas, onde algumas dessas pessoas possuem a possibilidade de acessar o sistema através de usuário e senha, também deseja-se permitir que outros sistemas autentiquem e tenham acesso os dados através de uma [API](https://pt.wikipedia.org/wiki/Interface_de_programa%C3%A7%C3%A3o_de_aplica%C3%A7%C3%B5es). Isso pode ser feito criando uma classe para representar os sistemas que terão permissão para acessar os dados. Exemplo: + +```python +class Sistema: + def __init__(self, usuario, senha): + self.usuario = usuario + self.senha = senha + + def autenticar(self, usuario, senha): + return self.usuario == usuario and self.senha == senha +``` + +Porém, esse código repete a implementação feita para `PessoaAutenticavel`: + +```python +class PessoaAutenticavel(Pessoa): + def __init__(self, nome, sobrenome, idade, usuario, senha): + super().__init__(nome, sobrenome, idade) + self.usuario = usuario + self.senha = senha + + def autenticar(self, usuario, senha): + return self.usuario == usuario and self.senha == senha +``` + +Aproveitando que Python, diferente de outras linguagens, possui herança múltipla, é possível extrair essa lógica das classes, centralizando a implementação em uma outra classe e simplesmente herdá-la. Exemplo: + +```python +class Autenticavel: + def __init__(self, *args, usuario, senha, **kwargs): + super().__init__(*args, **kwargs) + self.usuario = usuario + self.senha = senha + + def autenticar(self, usuario, senha): + return self.usuario == usuario and self.senha == senha + + +class PessoaAutenticavel(Autenticavel, Pessoa): + ... + + +class Sistema(Autenticavel): + ... + + +p = PessoaAutenticavel(nome='João', sobrenome='da Silva', idade=20, + usuario='joao', senha='secreta') +``` + +A primeira coisa a ser observada são os argumentos `*args` e `**kwargs` no `__init__` da classe `Autenticavel`, eles são usados uma vez que não se sabe todos os argumentos que o `__init__` da classe que estenderá o `Autenticavel` espera receber, funcionando de forma dinâmica (mais sobre esse recurso pode ser visto na [documentação do Python](https://docs.python.org/pt-br/3/tutorial/controlflow.html#more-on-defining-functions)). + +A segunda coisa a ser verificada é que para a classe `PessoaAutenticavel`, agora cria em seus objetos, a estrutura tanto da classe `Pessoa`, quanto `Autenticavel`. Algo similar a versão sem orientação a objetos a baixo: + +```python +# Arquivo: pessoa_autenticavel.py + +import autenticavel +import pessoa + + +def init(p, nome, sobrenome, idade, usuario, senha): + pessoa.init(p, nome, sobrenome, idade) + autenticavel.init(p, usuario, senha) +``` + +Também vale observar que as classes `PessoaAutenticavel` e `Sistema` não precisam definir nenhuma função, uma vez que elas cumprem seus papéis apenas herdando outras classes, porém seria possível implementar funções específicas dessas classes, assim como sobrescrever as funções definidas por outras classes. + +## Ordem de resolução de métodos + +Embora herança múltiplas sejam interessantes, existe um problema, se ambas as classes pai possuírem uma função com um mesmo nome, a classe filha deveria chamar qual das funções? A do primeiro pai? A do último? Para lidar com esse problema o Python usa o MRO (*method resolution order*, ordem de resolução do método), que consiste em uma tupla com a ordem de qual classe o Python usará para encontrar o método a ser chamado. Exemplo: + +```python +print(PessoaAutenticavel.__mro__) +# (, , , ) +``` + +Por esse motivo que também foi possível chamar o `super().__init__` dentro de `Autenticavel`, que devido ao MRO, o Python chama o `__init__` da outra classe pai da classe que estendeu `Autenticavel`, em vez de precisar fazer um método `__init__` em `PessoaAutenticavel` chamando o `__init__` de todas as suas classes pais, como foi feito na versão sem orientação a objetos. E por isso a ordem `Autenticavel` e `Pessoa` na herança de `PessoaAutenticavel`, para fazer o MRO procurar os métodos primeiro em `Autenticavel` e depois em `Pessoa`. + +Para tentar fugir da complexidade que pode ser herança múltipla, é possível escrever classes que tem por objetivo unicamente incluir alguma funcionalidade em outra, como o caso da classe `Autenticavel`, que pode ser herdada por qualquer outra classe do sistema para permitir o acesso ao sistema. Essas classes recebem o nome de mixins, e adiciona uma funcionalidade bem definida. + +## Estendendo mixins + +Imagine se além de permitir o acesso ao sistema, também gostaríamos de registrar algumas tentativas de acesso, informando quando houve a tentativa e se o acesso foi concedido ou não. Como `Autenticavel` é uma classe, é possível extendê-la para implementar essa funcionalidade na função `autenticar`. Exemplo: + +```python +from datetime import datetime + + +class AutenticavelComRegistro(Autenticavel): + @staticmethod + def _get_data(): + return datetime.now().strftime('%d/%m/%Y %T') + + def autenticar(self, usuario, senha): + print(f'{self._get_data()} Tentativa de acesso de {usuario}') + acesso = super().autenticar(usuario, senha) + if acesso: + acesso_str = 'permitido' + else: + acesso_str = 'negado' + print(f'{self._get_data()} Acesso de {usuario} {acesso_str}') + return acesso + + +class PessoaAutenticavelComRegistro(AutenticavelComRegistro, Pessoa): + ... + + +class SistemaAutenticavelComRegistro(AutenticavelComRegistro, Sistema): + ... + + +p = PessoaAutenticavelComRegistro( + nome='João', sobrenome='da Silva', idade=20, + usuario='joao', senha='secreta', +) +p.autenticar('joao', 'secreta') +# Saída na tela: +# 23/04/2021 16:56:58 Tentativa de acesso de joao +# 23/04/2021 16:56:58 Acesso de joao permitido +``` + +Essa implementação utiliza-se do `super()` para acessar a função `autenticar` da classe `Autenticavel` para não precisar reimplementar a autenticação. Porém, antes de chamá-la, manipula seus argumentos para registrar quem tentou acessar o sistema, assim como também manipula o seu retorno para registrar se o acesso foi permitido ou não. + +Essa classe também permite analisar melhor a ordem em que as classes são consultadas quando uma função é chamada: + +```python +print(PessoaAutenticavelComRegistro.__mro__) +# (, , , , ) +``` + +Que também pode ser visto na forma de um digrama de classes: + +![Diagrama de classes](images/eduardoklosowski/oo-de-outra-forma-4/mro.png) + +Onde é feito uma [busca em profundidade](https://pt.wikipedia.org/wiki/Busca_em_profundidade), como se a função fosse chamada no primeiro pai, e só se ela não for encontrada, busca-se no segundo pai e assim por diante. Também é possível observar a classe `object`, que sempre será a última classe, e é a classe pai de todas as outras classes do Python quando elas não possuirem um pai declarado explicitamente. + +## Considerações + +Herança múltipla pode dificultar bastante o entendimento do código, principalmente para encontrar onde determinada função está definida, porém pode facilitar bastante o código. Um exemplo que usa bastante herança e mixins são as *views* baseadas em classe do django ([*class-based views*](https://docs.djangoproject.com/pt-br/3.2/topics/class-based-views/)), porém para facilitar a visualização existe o site [Classy Class-Based Views](https://ccbv.co.uk/) que lista todas as classes, e os mixins utilizados em cada uma, como pode ser visto em "Ancestors" como na [UpdateView](https://ccbv.co.uk/projects/Django/3.1/django.views.generic.edit/UpdateView/), que é usado para criar uma página com formulário para editar um registro já existente no banco, assim ela usa mixins para pegar um objeto do banco (`SingleObjectMixin`), processar formulário baseado em uma tabela do banco (`ModelFormMixin`) e algumas outras funcionalidades necessárias para implementar essa página. + +--- + +Esse artigo foi publicado originalmente no [meu blog](https://eduardoklosowski.github.io/blog/), passe por lá, ou siga-me no [DEV](https://dev.to/eduardoklosowski) para ver mais artigos que eu escrevi.