|
| 1 | +Title: Encapsulamento da lógica do algoritmo |
| 2 | +Slug: encapsulamento-da-logica-do-algoritmo |
| 3 | +Date: 2021-03-02 15:00 |
| 4 | +Category: Python |
| 5 | +Tags: python, poo |
| 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, estuda DevOps |
| 12 | + |
| 13 | +Muitas listas de exercícios de lógica de programação pedem em algum momento que um valor seja lido do teclado, e caso esse valor seja inválido, deve-se avisar, e repetir a leitura até que um valor válido seja informado. Utilizando a ideia de [otimização do algoritmo passo a passo](https://dev.to/acaverna/otimizando-o-algoritmo-passo-a-passo-4co0), começando com uma solução simples, pretendo estudar como reduzir a duplicação de código alterando o algoritmo, encapsulando a lógica em funções, e encapsulando em classes. |
| 14 | + |
| 15 | +## Exercício |
| 16 | + |
| 17 | +Um exemplo de exercício que pede esse tipo de validação é a leitura de notas, que devem estar entre 0 e 10. A solução mais simples, consiste em ler um valor, e enquanto esse valor for inválido, dar o aviso e ler outro valor. Exemplo: |
| 18 | + |
| 19 | +```python |
| 20 | +nota = float(input('Digite a nota: ')) |
| 21 | +while nota < 0 or nota > 10: |
| 22 | + print('Nota inválida') |
| 23 | + nota = float(input('Digite a nota: ')) |
| 24 | +``` |
| 25 | + |
| 26 | +Esse algoritmo funciona, porém existe uma duplicação no código que faz a leitura da nota (uma antes do *loop* e outra dentro). Caso seja necessário uma alteração, como a mudança da nota para um valor inteiro entre 0 e 100, deve-se alterar os dois lugares, e se feito em apenas um lugar, o algoritmo poderia processar valores inválidos. |
| 27 | + |
| 28 | +### Alterando o algoritmo |
| 29 | + |
| 30 | +Visando remover a repetição de código, é possível unificar a leitura do valor dentro do *loop*, uma vez que é necessário repetir essa instrução até que o valor válido seja obtido. Exemplo: |
| 31 | + |
| 32 | +```python |
| 33 | +while True: |
| 34 | + nota = float(input('Digite a nota: ')) |
| 35 | + if 0 <= nota <= 10: |
| 36 | + break |
| 37 | + print('Nota inválida!') |
| 38 | +``` |
| 39 | + |
| 40 | +Dessa forma, não existe mais a repetição de código. A condição de parada, que antes verificava se o valor era inválido (o que pode ter uma leitura não tão intuitiva), agora verifica se é um valor válido (que é geralmente é mais fácil de ler e escrever a condição). E a ordem dos comandos dentro do *loop*, que agora estão em uma ordem que facilita a leitura, visto que no algoritmo anterior era necessário tem em mente o que era executado antes do *loop*. |
| 41 | + |
| 42 | +Porém esses algoritmos validam apenas o valor lido, apresentando erro caso seja informado um valor com formato inválido, como letras em vez de números. Isso pode ser resolvido tratando as exceções lançadas. Exemplo: |
| 43 | + |
| 44 | +```python |
| 45 | +while True: |
| 46 | + try: |
| 47 | + nota = float(input('Digite a nota: ')) |
| 48 | + if 0 <= nota <= 10: |
| 49 | + break |
| 50 | + except ValueError: |
| 51 | + ... |
| 52 | + print('Nota inválida!') |
| 53 | +``` |
| 54 | + |
| 55 | +### Encapsulamento da lógica em função |
| 56 | + |
| 57 | +Caso fosse necessário ler várias notas, com os algoritmos apresentados até então, seria necessário repetir todo esse trecho de código, ou utilizá-lo dentro de uma estrutura de repetição. Para facilitar sua reutilização, evitando a duplicação de código, é possível encapsular esse algoritmo dentro de uma função. Exemplo: |
| 58 | + |
| 59 | +```python |
| 60 | +def nota_input(prompt): |
| 61 | + while True: |
| 62 | + try: |
| 63 | + nota = float(input(prompt)) |
| 64 | + if 0 <= nota <= 10: |
| 65 | + break |
| 66 | + except ValueError: |
| 67 | + ... |
| 68 | + print('Nota inválida!') |
| 69 | + return nota |
| 70 | + |
| 71 | + |
| 72 | +nota1 = nota_input('Digite a primeira nota: ') |
| 73 | +nota2 = nota_input('Digite a segunda nota: ') |
| 74 | +``` |
| 75 | + |
| 76 | +### Encapsulamento da lógica em classes |
| 77 | + |
| 78 | +Em vez de encapsular essa lógica em uma função, é possível encapsulá-la em uma classe, o que permitiria separar cada etapa do algoritmo em métodos, assim como ter um método responsável por controlar qual etapa deveria ser chamada em qual momento. Exemplo: |
| 79 | + |
| 80 | +```python |
| 81 | +class ValidaNotaInput: |
| 82 | + mensagem_valor_invalido = 'Nota inválida!' |
| 83 | + |
| 84 | + def ler_entrada(self, prompt): |
| 85 | + return input(prompt) |
| 86 | + |
| 87 | + def transformar_entrada(self, entrada): |
| 88 | + return float(entrada) |
| 89 | + |
| 90 | + def validar_nota(self, nota): |
| 91 | + return 0 <= nota <= 10 |
| 92 | + |
| 93 | + def __call__(self, prompt): |
| 94 | + while True: |
| 95 | + try: |
| 96 | + nota = self.transformar_entrada(self.ler_entrada(prompt)) |
| 97 | + if self.validar_nota(nota): |
| 98 | + break |
| 99 | + except ValueError: |
| 100 | + ... |
| 101 | + print(self.mensagem_valor_invalido) |
| 102 | + return nota |
| 103 | + |
| 104 | + |
| 105 | +nota_input = ValidaNotaInput() |
| 106 | + |
| 107 | + |
| 108 | +nota = nota_input('Digite a nota: ') |
| 109 | +``` |
| 110 | + |
| 111 | +Vale observar que o método `__call__` permite que o objeto criado a partir dessa classe seja chamado como se fosse uma função. Nesse caso ele é o responsável por chamar cada etapa do algoritmo, como: `ler_entrada` que é responsável por ler o que foi digitado no teclado, `transformar_entrada` que é responsável por converter o texto lido para o tipo desejado (converter de `str` para `float`), e `validar_nota` que é responsável por dizer se o valor é válido ou não. Vale observar que ao dividir o algoritmo em métodos diferentes, seu código principal virou uma espécie de código comentado, descrevendo o que está sendo feito e onde está sendo feito. |
| 112 | + |
| 113 | +Outra vantagem de encapsular a lógica em classe, em vez de uma função, é a possibilidade de generalizá-la. Se fosse necessário validar outro tipo de entrada, encapsulando em uma função, seria necessário criar outra função repetindo todo o algoritmo, alterando apenas a parte referente a transformação do valor lido, e validação, o que gera uma espécie de repetição de código. Ao encapsular em classes, é possível se aproveitar dos mecanismos de herança para evitar essa repetição. Exemplo: |
| 114 | + |
| 115 | +```python |
| 116 | +class ValidaInput: |
| 117 | + mensagem_valor_invalido = 'Valor inválido!' |
| 118 | + |
| 119 | + def ler_entrada(self, prompt): |
| 120 | + return input(prompt) |
| 121 | + |
| 122 | + def transformar_entrada(self, entrada): |
| 123 | + raise NotImplementedError |
| 124 | + |
| 125 | + def validar_valor(self, valor): |
| 126 | + raise NotImplementedError |
| 127 | + |
| 128 | + def __call__(self, prompt): |
| 129 | + while True: |
| 130 | + try: |
| 131 | + valor = self.transformar_entrada(self.ler_entrada(prompt)) |
| 132 | + if self.validar_valor(valor): |
| 133 | + break |
| 134 | + except ValueError: |
| 135 | + ... |
| 136 | + print(self.mensagem_valor_invalido) |
| 137 | + return valor |
| 138 | + |
| 139 | + |
| 140 | +class ValidaNomeInput(ValidaInput): |
| 141 | + mensagem_valor_invalido = 'Nome inválido!' |
| 142 | + |
| 143 | + def transformar_entrada(self, entrada): |
| 144 | + return entrada.strip().title() |
| 145 | + |
| 146 | + def validar_valor(self, valor): |
| 147 | + return valor != '' |
| 148 | + |
| 149 | + |
| 150 | +class ValidaNotaInput(ValidaInput): |
| 151 | + mensagem_valor_invalido = 'Nota inválida!' |
| 152 | + |
| 153 | + def transformar_entrada(self, entrada): |
| 154 | + return float(entrada) |
| 155 | + |
| 156 | + def validar_valor(self, valor): |
| 157 | + return 0 <= valor <= 10 |
| 158 | + |
| 159 | + |
| 160 | +nome_input = ValidaNomeInput() |
| 161 | +nota_input = ValidaNotaInput() |
| 162 | + |
| 163 | + |
| 164 | +nome = nome_input('Digite o nome: ') |
| 165 | +nota = nota_input('Digite a nota: ') |
| 166 | +``` |
| 167 | + |
| 168 | +Dessa forma, é possível reutilizar o código já existente para criar outras validações, sendo necessário implementar apenas como converter a `str` lida do teclado para o tipo desejado, e como esse valor deve ser validado. Não é necessário entender e repetir a lógica de ler o valor, validá-lo, imprimir a mensagem de erro, e repetir até que seja informado um valor válido. |
| 169 | + |
| 170 | +## Considerações |
| 171 | + |
| 172 | +É possível encapsular a lógica de um algoritmo em funções ou em classes. Embora para fazê-lo em uma classe exija conhecimentos de programação orientada a objetos, o seu reaproveitamento é facilitado, abstraindo toda a complexidade do algoritmo, que pode ser disponibilizado através de uma biblioteca, exigindo apenas a implementações de métodos simples por quem for a utilizar. |
| 173 | + |
| 174 | +Ainda poderia ser discutido outras formas de fazer essa implementação, como passar funções como parâmetro e a utilização de [corrotinas](https://docs.python.org/pt-br/3/library/asyncio-task.html) no encapsulamento do algoritmo em função, assim como a utilização de [classmethod](https://docs.python.org/pt-br/3/library/functions.html#classmethod), [staticmethod](https://docs.python.org/pt-br/3/library/functions.html#staticmethod) e [ABC](https://docs.python.org/pt-br/3/library/abc.html) no encapsulamento do algoritmo em classes. |
| 175 | + |
| 176 | +--- |
| 177 | + |
| 178 | +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