Skip to content

Commit 08840ea

Browse files
authored
Merge pull request #319 from eduardoklosowski/artigo
Adiciona artigo: Orientação a objetos de outra forma: ABC
2 parents bf9732b + 6975bca commit 08840ea

File tree

1 file changed

+236
-0
lines changed

1 file changed

+236
-0
lines changed

content/oo-de-outra-forma-5.md

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
Title: Orientação a objetos de outra forma: ABC
2+
Slug: oo-de-outra-forma-5
3+
Date: 2021-05-10 12:00
4+
Category: Python
5+
Tags: python, orientação a objetos
6+
Author: Eduardo Klosowski
7+
8+
Github: eduardoklosowski
9+
Twitter: eduklosowski
10+
Site: https://dev.to/eduardoklosowski
11+
About_author: Programador, formado em redes de computadores e estuda DevOps
12+
13+
Na discussão sobre [herança e mixins](https://dev.to/acaverna/orientacao-a-objetos-de-outra-forma-heranca-multiplas-e-mixins-31eb) foram criadas várias classes, como `Autenticavel` e `AutenticavelComRegistro` que adicionam funcionalidades a outras classes e implementavam tudo o que precisavam para seu funcionamento. Entretanto podem existir casos em que não seja possível implementar todas as funções na própria classe, deixando com que as classes que a estende implemente essas funções. Uma forma de fazer isso é través das [ABC](https://docs.python.org/pt-br/3/library/abc.html) (*abstract base classes*, ou classes base abstratas).
14+
15+
## Sem uso de classes base abstratas
16+
17+
Um exemplo de classe que não é possível implementar todas as funcionalidades foi dada no texto [Encapsulamento da lógica do algoritmo](https://dev.to/acaverna/encapsulamento-da-logica-do-algoritmo-298e), que discutia a leitura de valores do teclado até que um valor válido fosse lido (ou que repete a leitura caso um valor inválido tivesse sido informado). Nesse caso a classe `ValidaInput` implementava a lógica base de funcionamento, porém eram suas classes filhas (`ValidaNomeInput` e `ValidaNotaInput`) que implementavam as funções para tratar o que foi lido do teclado e verificar se é um valor válido ou não.
18+
19+
```python
20+
class ValidaInput:
21+
mensagem_valor_invalido = 'Valor inválido!'
22+
23+
def ler_entrada(self, prompt):
24+
return input(prompt)
25+
26+
def transformar_entrada(self, entrada):
27+
raise NotImplementedError
28+
29+
def validar_valor(self, valor):
30+
raise NotImplementedError
31+
32+
def __call__(self, prompt):
33+
while True:
34+
try:
35+
valor = self.transformar_entrada(self.ler_entrada(prompt))
36+
if self.validar_valor(valor):
37+
break
38+
except ValueError:
39+
...
40+
print(self.mensagem_valor_invalido)
41+
return valor
42+
43+
44+
class ValidaNomeInput(ValidaInput):
45+
mensagem_valor_invalido = 'Nome inválido!'
46+
47+
def transformar_entrada(self, entrada):
48+
return entrada.strip().title()
49+
50+
def validar_valor(self, valor):
51+
return valor != ''
52+
53+
54+
class ValidaNotaInput(ValidaInput):
55+
mensagem_valor_invalido = 'Nota inválida!'
56+
57+
def transformar_entrada(self, entrada):
58+
return float(entrada)
59+
60+
def validar_valor(self, valor):
61+
return 0 <= valor <= 10
62+
```
63+
64+
Entretanto, esse código permite a criação de objetos da classe `ValidaInput` mesmo sem ter uma implementação das funções `transformar_entrada` e `validar_valor`. E a única mensagem de erro ocorreria ao tentar executar essas funções, o que poderia estar longe do problema real, que é a criação de um objeto a partir de uma classe que não prove todas as implementações das suas funções, o que seria semelhante a uma classe abstrata em outras linguagens.
65+
66+
```python
67+
obj = ValidaInput()
68+
69+
# Diversas linhas de código
70+
71+
obj('Entrada: ') # Exceção NotImplementedError lançada
72+
```
73+
74+
## Com uso de classes base abstratas
75+
76+
Seguindo a documentação da [ABC](https://docs.python.org/pt-br/3/library/abc.html), para utilizá-las é necessário informar a metaclasse `ABCMeta` na criação da classe, ou simplesmente estender a classe `ABC`, e decorar com `abstractmethod` as funções que as classes que a estenderem deverão implementar. Exemplo:
77+
78+
```python
79+
from abc import ABC, abstractmethod
80+
81+
82+
class ValidaInput(ABC):
83+
mensagem_valor_invalido = 'Valor inválido!'
84+
85+
def ler_entrada(self, prompt):
86+
return input(prompt)
87+
88+
@abstractmethod
89+
def transformar_entrada(self, entrada):
90+
...
91+
92+
@abstractmethod
93+
def validar_valor(self, valor):
94+
...
95+
96+
def __call__(self, prompt):
97+
while True:
98+
try:
99+
valor = self.transformar_entrada(self.ler_entrada(prompt))
100+
if self.validar_valor(valor):
101+
break
102+
except ValueError:
103+
...
104+
print(self.mensagem_valor_invalido)
105+
return valor
106+
```
107+
108+
Desta forma, ocorrerá um erro já ao tentar criar um objeto do tipo `ValidaInput`, dizendo quais são as funções que precisam ser implementadas. Porém funcionará normalmente ao criar objetos a partir das classes `ValidaNomeInput` e `ValidaNotaInput` visto que elas implementam essas funções.
109+
110+
```python
111+
obj = ValidaInput() # Exceção TypeError lançada
112+
113+
nome_input = ValidaNomeInput() # Objeto criado
114+
nota_input = ValidaNotaInput() # Objeto criado
115+
```
116+
117+
Como essas funções não utilizam a referência ao objeto (`self`), ainda é possível decorar as funções com `staticmethod`, como:
118+
119+
```python
120+
from abc import ABC, abstractmethod
121+
122+
123+
class ValidaInput(ABC):
124+
mensagem_valor_invalido = 'Valor inválido!'
125+
126+
@staticmethod
127+
def ler_entrada(prompt):
128+
return input(prompt)
129+
130+
@staticmethod
131+
@abstractmethod
132+
def transformar_entrada(entrada):
133+
...
134+
135+
@staticmethod
136+
@abstractmethod
137+
def validar_valor(valor):
138+
...
139+
140+
def __call__(self, prompt):
141+
while True:
142+
try:
143+
valor = self.transformar_entrada(self.ler_entrada(prompt))
144+
if self.validar_valor(valor):
145+
break
146+
except ValueError:
147+
...
148+
print(self.mensagem_valor_invalido)
149+
return valor
150+
151+
152+
class ValidaNomeInput(ValidaInput):
153+
mensagem_valor_invalido = 'Nome inválido!'
154+
155+
@staticmethod
156+
def transformar_entrada(entrada):
157+
return entrada.strip().title()
158+
159+
@staticmethod
160+
def validar_valor(valor):
161+
return valor != ''
162+
163+
164+
class ValidaNotaInput(ValidaInput):
165+
mensagem_valor_invalido = 'Nota inválida!'
166+
167+
@staticmethod
168+
def transformar_entrada(entrada):
169+
return float(entrada)
170+
171+
@staticmethod
172+
def validar_valor(valor):
173+
return 0 <= valor <= 10
174+
```
175+
176+
Isso também seria válido para funções decoradas com `classmethod`, que receberiam a referência a classe (`cls`).
177+
178+
## Considerações
179+
180+
Não é necessário utilizar ABC para fazer o exemplo discutido, porém ao utilizar essa biblioteca ficou mais explícito quais as funções que precisavam ser implementados nas classes filhas, ainda mais que sem utilizar ABC a classe base poderia nem ter as funções, com:
181+
182+
```python
183+
class ValidaInput:
184+
mensagem_valor_invalido = 'Valor inválido!'
185+
186+
def ler_entrada(self, prompt):
187+
return input(prompt)
188+
189+
def __call__(self, prompt):
190+
while True:
191+
try:
192+
valor = self.transformar_entrada(self.ler_entrada(prompt))
193+
if self.validar_valor(valor):
194+
break
195+
except ValueError:
196+
...
197+
print(self.mensagem_valor_invalido)
198+
return valor
199+
```
200+
201+
Como Python possui [duck-typing](https://docs.python.org/pt-br/3/glossary.html#term-duck-typing), não é necessário uma grande preocupação com os tipos, como definir e utilizar interfaces presentes em outras implementações de orientação a objetos, porém devido à herança múltipla, ABC pode ser utilizada como interface que não existe em Python, fazendo com que as classes implementem determinadas funções. Para mais a respeito desse assunto, recomendo as duas lives do dunossauro sobre ABC ([1](https://www.youtube.com/watch?v=yLHV1__nZZw) e [2](https://www.youtube.com/watch?v=erAXvsuihPQ)), e a apresentação do Luciano Ramalho sobre [type hints](https://www.youtube.com/watch?v=AJK2LqrlnTE).
202+
203+
Uma classe filha também não é obrigada a implementar todas as funções decoradas com `abstractmethod`, mas assim como a classe pai, não será possível criar objetos a partir dessa classe, apenas de uma classe filha dela que implemente as demais funções. Como se ao aplicar um `abstractmethod` tornasse a classe abstrata, e qualquer classe filha só deixasse de ser abstrata quando a última função decorada com `abstractmethod` for sobrescrita. Exemplo:
204+
205+
```python
206+
from abc import ABC, abstractmethod
207+
208+
209+
class A(ABC):
210+
@abstractmethod
211+
def func1(self):
212+
...
213+
214+
@abstractmethod
215+
def func2(self):
216+
...
217+
218+
219+
class B(A):
220+
def func1(self):
221+
print('1')
222+
223+
224+
class C(B):
225+
def func2(self):
226+
print('2')
227+
228+
229+
a = A() # Erro por não implementar func1 e func2
230+
b = B() # Erro por não implementar func2
231+
c = C() # Objeto criado
232+
```
233+
234+
---
235+
236+
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.

0 commit comments

Comments
 (0)