Essa pergunta é um pouco independente da linguagem, mas não completamente, pois a Programação Orientada a Objetos (OOP) é diferente em, por exemplo, Java , que não possui funções de primeira classe, do que em Python .
Em outras palavras, me sinto menos culpado por criar classes desnecessárias em uma linguagem como Java, mas sinto que pode haver uma maneira melhor nas linguagens menos padronizadas como o Python.
Meu programa precisa executar uma operação relativamente complexa várias vezes. Essa operação requer muita "contabilidade", tem que criar e excluir alguns arquivos temporários, etc.
É por isso que ele também precisa chamar muitas outras "suboperações" - colocar tudo em um método enorme não é muito bom, modular, legível etc.
Agora, essas são abordagens que vêm à minha mente:
1. Crie uma classe que tenha apenas um método público e mantenha o estado interno necessário para as suboperações em suas variáveis de instância.
Seria algo como isto:
class Thing:
def __init__(self, var1, var2):
self.var1 = var1
self.var2 = var2
self.var3 = []
def the_public_method(self, param1, param2):
self.var4 = param1
self.var5 = param2
self.var6 = param1 + param2 * self.var1
self.__suboperation1()
self.__suboperation2()
self.__suboperation3()
def __suboperation1(self):
# Do something with self.var1, self.var2, self.var6
# Do something with the result and self.var3
# self.var7 = something
# ...
self.__suboperation4()
self.__suboperation5()
# ...
def suboperation2(self):
# Uses self.var1 and self.var3
# ...
# etc.
O problema que vejo com essa abordagem é que o estado dessa classe só faz sentido internamente e não pode fazer nada com suas instâncias, exceto chamar seu único método público.
# Make a thing object
thing = Thing(1,2)
# Call the only method you can call
thing.the_public_method(3,4)
# You don't need thing anymore
2. Crie várias funções sem uma classe e passe as várias variáveis necessárias internamente entre elas (como argumentos).
O problema que vejo com isso é que tenho que passar muitas variáveis entre funções. Além disso, as funções estariam intimamente relacionadas entre si, mas não seriam agrupadas.
3. Como 2., mas torne as variáveis de estado globais em vez de passá-las.
Isso não seria bom, pois tenho que fazer a operação mais de uma vez, com entradas diferentes.
Existe uma quarta abordagem melhor? Caso contrário, qual dessas abordagens seria melhor e por quê? Tem algo que estou perdendo?
fonte
Respostas:
A opção 1 é um bom exemplo de encapsulamento usado corretamente. Você deseja que o estado interno seja oculto do código externo.
Se isso significa que sua classe possui apenas um método público, que assim seja. Vai ser muito mais fácil de manter.
No OOP, se você tem uma classe que faz exatamente uma coisa, tem uma pequena superfície pública e mantém todo o seu estado interno oculto, então você está (como diria Charlie Sheen) GANHANDO .
A opção 2 sofre de baixa coesão . Isso tornará a manutenção mais difícil.
A opção 3, como a opção 2, sofre de baixa coesão, mas muito mais severamente!
A história mostrou que a conveniência das variáveis globais é superada pelo brutal custo de manutenção que ela traz. É por isso que você ouve peidos velhos como eu reclamando sobre encapsulamento o tempo todo.
A opção vencedora é a nº 1 .
fonte
ThingDoer(var1, var2).do_it()
vs.do_thing(var1, var2)
.def do_thing(var1, var2): return _ThingDoer(var1, var2).run()
, para tornar a API externa um pouco mais bonita.Eu acho que # 1 é realmente uma má opção.
Vamos considerar sua função:
Quais dados a suboperação1 usa? Coleta dados usados pela suboperação2? Quando você repassa dados armazenando-os, não sei como as partes da funcionalidade estão relacionadas. Quando olho para mim mesmo, alguns dos atributos são do construtor, alguns da chamada para o método public_public e alguns adicionados aleatoriamente em outros lugares. Na minha opinião, é uma bagunça.
E o número 2? Bem, primeiro, vejamos o segundo problema com ele:
Eles estariam em um módulo juntos, para que fossem totalmente agrupados.
Na minha opinião, isso é bom. Isso torna explícitas as dependências de dados no seu algoritmo. Ao armazená-las, seja em variáveis globais ou em um eu, você oculta as dependências e as faz parecer menos ruins, mas elas ainda estão lá.
Geralmente, quando essa situação surge, significa que você não encontrou o caminho certo para decompor seu problema. Você está achando estranho dividir em várias funções porque está tentando dividir da maneira errada.
Obviamente, sem ver sua função real, é difícil adivinhar o que seria uma boa sugestão. Mas você dá uma dica do que está lidando aqui:
Deixe-me escolher um exemplo de algo que se encaixa na sua descrição, um instalador. Um instalador precisa copiar um monte de arquivos, mas se você o cancelar parcialmente, será necessário retroceder todo o processo, incluindo a devolução de todos os arquivos que você substituiu. O algoritmo para isso se parece com:
Agora multiplique essa lógica por ter que fazer configurações de registro, ícones de programas etc., e você terá uma bagunça.
Penso que a sua solução # 1 se parece com:
Isso torna o algoritmo geral mais claro, mas oculta a maneira como as diferentes partes se comunicam.
A abordagem nº 2 se parece com:
Agora é explícito como partes dos dados se movem entre funções, mas é muito estranho.
Então, como essa função deve ser escrita?
O objeto FileTransactionLog é um gerenciador de contexto. Quando copy_new_files copia um arquivo, ele é feito através do FileTransactionLog, que lida com a cópia temporária e controla quais arquivos foram copiados. No caso de exceção, ele copia os arquivos originais de volta e, no caso de êxito, exclui as cópias temporárias.
Isso funciona porque encontramos uma decomposição mais natural da tarefa. Anteriormente, estávamos misturando a lógica sobre como instalar o aplicativo com a lógica sobre como recuperar uma instalação cancelada. Agora, o log de transações lida com todos os detalhes sobre arquivos temporários e contabilidade, e a função pode se concentrar no algoritmo básico.
Suspeito que o seu caso esteja no mesmo barco. Você precisa extrair os elementos da contabilidade em algum tipo de objeto para que sua tarefa complexa possa ser expressa de maneira mais simples e elegante.
fonte
Como a única desvantagem aparente do método 1 é seu padrão de uso subótimo, acho que a melhor solução é levar o encapsulamento um passo adiante: use uma classe, mas também forneça uma função autônoma, que apenas constrói o objeto, chama o método e retorna :
Com isso, você tem a menor interface possível para o seu código, e a classe que você usa internamente se torna realmente apenas um detalhe de implementação da sua função pública. O código de chamada nunca precisa saber sobre sua classe interna.
fonte
Por um lado, a questão é, de alguma forma, independente da linguagem; mas, por outro lado, a implementação depende da linguagem e de seus paradigmas. Nesse caso, é o Python, que suporta múltiplos paradigmas.
Além de suas soluções, há também a possibilidade de executar as operações sem apátrida de uma maneira mais funcional, por exemplo
Tudo se resume a
Mas -Se a sua base de código for OOP, ela violará a consistência se de repente algumas partes forem escritas em um estilo (mais) funcional.
fonte
Por que projetar quando posso codificar?
Ofereço uma visão contrária às respostas que li. Nessa perspectiva, todas as respostas e, francamente, até a própria pergunta, se concentram na mecânica da codificação. Mas esse é um problema de design.
Sim, pois faz sentido para o seu design. Pode ser uma classe em si ou parte de outra classe ou seu comportamento pode ser distribuído entre as classes.
Design Orientado a Objetos é sobre Complexidade
O objetivo do OO é criar e manter com êxito sistemas grandes e complexos, encapsulando o código em termos do próprio sistema. O design "adequado" diz que tudo está em alguma classe.
O design de OO gerencia naturalmente a complexidade principalmente por meio de classes focadas que aderem ao princípio de responsabilidade única. Essas classes fornecem estrutura e funcionalidade ao longo da respiração e profundidade do sistema, interagem e integram essas dimensões.
Dado isso, é comum dizer que funções pendentes no sistema - a classe de utilidade geral onipresente demais - é um cheiro de código que sugere um design insuficiente. Eu tendem a concordar.
fonte
Por que não comparar suas necessidades com algo que existe na biblioteca padrão do Python e depois ver como isso é implementado?
Observe que, se você não precisar de um objeto, ainda poderá definir funções nas funções. Com o Python 3, existe a nova
nonlocal
declaração para permitir que você altere variáveis em sua função pai.Você ainda pode achar útil ter algumas classes privadas simples dentro de sua função para implementar abstrações e organizar operações.
fonte
nonlocal
nenhum lugar nas minhas bibliotecas python atualmente instaladas. Talvez você possa entender a partir detextwrap.py
qualTextWrapper
classe, mas também umadef wrap(text)
função que simplesmente cria umaTextWrapper
instância, chama o.wrap()
método nela e retorna. Portanto, use uma classe, mas adicione algumas funções de conveniência.