Estou apenas projetando meu aplicativo e não tenho certeza se entendi corretamente o SOLID e o OOP. As classes devem fazer uma coisa e fazê-lo bem, mas, por outro lado, devem representar objetos reais com os quais trabalhamos.
No meu caso, faço uma extração de recursos em um conjunto de dados e, em seguida, faço uma análise de aprendizado de máquina. Eu suponho que eu poderia criar três classes
- FeatureExtractor
- DataSet
- Analisador
Mas a classe FeatureExtractor não representa nada, faz algo que a torna mais uma rotina do que uma classe. Ele terá apenas uma função que será usada: extract_features ()
É correto criar classes que não representam uma coisa, mas fazem uma coisa?
EDIT: não tenho certeza se isso importa, mas eu estou usando Python
E se extract_features () ficaria assim: vale a pena criar uma classe especial para armazenar esse método?
def extract_features(df):
extr = PhrasesExtractor()
extr.build_vocabulary(df["Text"].tolist())
sent = SentimentAnalyser()
sent.load()
df = add_features(df, extr.features)
df = mark_features(df, extr.extract_features)
df = drop_infrequent_features(df)
df = another_processing1(df)
df = another_processing2(df)
df = another_processing3(df)
df = set_sentiment(df, sent.get_sentiment)
return df
fonte
Respostas:
Sim, essa geralmente é uma boa abordagem.
Não, isso é um equívoco comum do IMHO. O acesso de um bom iniciante ao POO geralmente é "começa com objetos que representam coisas do mundo real" , isso é verdade.
No entanto, você não deve parar com isso !
As aulas podem (e devem) ser usadas para estruturar seu programa de várias maneiras. Modelar objetos do mundo real é um aspecto disso, mas não o único. Criar módulos ou componentes para uma tarefa específica é outro caso de uso sensato para classes. Um "extrator de recursos" provavelmente é um módulo desse tipo e, mesmo que contenha apenas um método público
extract_features()
, eu ficaria surpreso se também não contenha muitos métodos privados e talvez algum estado compartilhado. Portanto, ter uma aulaFeatureExtractor
introduzirá um local natural para esses métodos particulares.Nota lateral: em linguagens como Python, que suportam um conceito de módulo separado, também é possível usar um módulo
FeatureExtractor
para isso, mas no contexto desta pergunta, isso é uma diferença desprezível no IMHO.Além disso, um "extrator de recurso" pode ser imaginado como "uma pessoa ou um bot que extrai recursos". Isso é uma "coisa" abstrata, talvez não seja uma coisa que você encontrará no mundo real, mas o nome em si é uma abstração útil, que dá a todos uma noção do que é a responsabilidade dessa classe. Então, eu discordo que essa classe não "representa nada".
fonte
extract_features
? Eu meio que assumi que elas eram funções públicas de outro lugar. É justo, eu concordo que, se forem privados, provavelmente devem entrar em um módulo (mas ainda assim: não em uma classe, a menos que compartilhem o estado), juntamente comextract_features
. (Dito isto, você poderia naturalmente declará-los localmente dentro dessa função.)Doc Brown é direto: as classes não precisam representar objetos do mundo real. Eles só precisam ser úteis . As classes são fundamentalmente apenas tipos adicionais, e o que corresponde
int
oustring
corresponde no mundo real? São descrições abstratas, não coisas concretas e tangíveis.Dito isto, seu caso é especial. De acordo com a sua descrição:
Você está absolutamente certo: se seu código é como mostrado, não adianta transformá-lo em uma classe. Há uma conversa famosa que argumenta que esses usos de classes no Python são cheiros de código e que funções simples são suficientes. Seu caso é um exemplo perfeito disso.
O uso excessivo de classes deve-se ao fato de o OOP ter se tornado popular com o Java nos anos 90. Infelizmente, na época, o Java carecia de vários recursos da linguagem moderna (como fechamentos), o que significa que muitos conceitos eram difíceis ou impossíveis de expressar sem o uso de classes. Por exemplo, até recentemente, era impossível em Java ter métodos que carregassem o estado (ou seja, fechamentos). Em vez disso, você precisava escrever uma classe para transportar o estado, e que expunha um único método (chamado algo como
invoke
).Infelizmente, esse estilo de programação tornou-se popular muito além do Java (em parte devido a um influente livro de engenharia de software que é muito útil), mesmo em linguagens que não exigem essas soluções alternativas.
No Python, as classes são obviamente uma ferramenta muito importante e devem ser usadas liberalmente. Mas eles não são a única ferramenta e não há razão para usá-los onde não fazem sentido. É um equívoco comum que funções livres não tenham lugar no OOP.
fonte
__call__
seja definido, caso em que apenas usar uma função!)__call__()
? Isso é realmente tão diferente de uma instância de classe interna anônima? Sintaticamente, com certeza, mas a partir de um design de linguagem, parece uma distinção menos significativa do que a que você apresenta aqui.Há mais de 20 anos, e também não tenho certeza.
Difícil de dar errado aqui.
Sério? Deixe-me apresentar-lhe a única classe mais popular e bem sucedido de todos os tempos:
String
. Nós o usamos para texto. E o objeto do mundo real que ele representa é o seguinte:Por que não, nem todos os programadores são obcecados com a pesca. Aqui estamos usando algo chamado metáfora. Não há problema em criar modelos de coisas que realmente não existem. É a ideia que deve ficar clara. Você está criando imagens na mente dos seus leitores. Essas imagens não precisam ser reais. Apenas entendi facilmente.
Um bom design de POO agrupa mensagens (métodos) em torno de dados (estado) para que as reações a essas mensagens possam variar dependendo desses dados. Se fazer isso modela algo do mundo real, elegante. Se não, tudo bem. Contanto que faça sentido para o leitor, tudo bem.
Agora, com certeza, você poderia pensar assim:
mas se você acha que isso precisa existir no mundo real antes de poder usar a metáfora, sua carreira em programação envolverá muitas artes e ofícios.
fonte
class
etable
ecolumn
...Cuidado! Em nenhum lugar o SOLID diz que uma classe deve "fazer apenas uma coisa". Se fosse esse o caso, as classes teriam apenas um único método e realmente não haveria diferença entre classes e funções.
O SOLID diz que uma classe deve representar uma única responsabilidade . São como as responsabilidades das pessoas de uma equipe: o motorista, o advogado, o batedor de carteiras, o designer gráfico etc. Cada uma dessas pessoas pode executar várias tarefas (relacionadas), mas todas pertencentes a uma única responsabilidade.
O ponto disso é - se houver uma alteração nos requisitos, você idealmente precisará modificar apenas uma única classe. Isso apenas torna o código mais fácil de entender, mais fácil de modificar e reduz os riscos.
Não há regra de que um objeto represente "uma coisa real". Este é apenas um conhecimento do culto à carga, uma vez que o OO foi inicialmente inventado para uso em simulações. Mas seu programa não é uma simulação (poucas aplicações OO modernas são), portanto, essa regra não se aplica. Contanto que cada classe tenha uma responsabilidade bem definida, você estará bem.
Se uma classe realmente possui apenas um método único e não possui nenhum estado, considere torná-la uma função autônoma. Isso é certo e segue os princípios KISS e YAGNI - não há necessidade de fazer uma classe se você puder resolvê-la com uma função. Por outro lado, se você tiver motivos para acreditar que pode precisar de um estado interno ou de várias implementações, também poderá torná-lo uma classe inicial. Você terá que usar seu melhor julgamento aqui.
fonte
Em geral, tudo bem.
Sem uma descrição um pouco mais específica, o que a
FeatureExtractor
classe deve fazer exatamente, é difícil dizer.De qualquer forma, mesmo que
FeatureExtractor
exponha apenas umaextract_features()
função pública , eu poderia pensar em configurá-la com umaStrategy
classe, que determina como exatamente a extração deve ser feita.Outro exemplo é uma classe com uma função Template .
E há mais padrões de design comportamental , baseados em modelos de classe.
Como você adicionou algum código para esclarecimento.
A linha
compreende exatamente o que eu quis dizer que você pode configurar uma classe com uma estratégia .
Se você tiver uma interface para essa
SentimentAnalyser
classe, poderá passá-la para aFeatureExtractor
classe em seu ponto de construção, em vez de se acoplar diretamente a essa implementação específica em sua função.fonte
FeatureExtractor
classe) apenas para introduzir ainda mais complexidade (uma interface para aSentimentAnalyser
classe). Se a dissociação for desejável, então a funçãoextract_features
pode ser aceitaget_sentiment
como argumento (aload
chamada parece ser independente da função e exigida apenas por seus efeitos). Observe também que o Python não possui / incentiva interfaces.CacheingFeatureExtractor
ou aTimeSeriesDependentFeatureExtractor
), um objeto seria um ajuste muito melhor. Só porque não há necessidade de um objeto atualmente não significa que nunca haverá.__call__
método será compatível se você precisar (você não vai), em quarto lugar, adicionando um invólucro comoFeatureExtractor
se estivesse tornando o código incompatível com todos os outros códigos já escritos (a menos que você forneça um__call__
método, nesse caso, uma função seria claramente mais simples )Padrões e toda a linguagem / conceitos sofisticados de lado: o que você encontrou foi um trabalho ou um processo em lote .
No final do dia, mesmo um programa de POO puro precisa, de alguma forma, ser conduzido por algo, para realmente executar o trabalho; deve haver um ponto de entrada de alguma forma. No padrão MVC, por exemplo, o ontroller "C" recebe eventos de clique etc. da GUI e orquestra os outros componentes. Nas ferramentas clássicas de linha de comando, uma função "principal" faria o mesmo.
Sua classe representa uma entidade que faz alguma coisa e orquestra todo o resto. Você pode nomear Controlador , Trabalho , Principal ou o que vier à sua mente.
Isso depende das circunstâncias (e eu não estou familiarizado com a maneira usual de fazer isso em Python). Se esta é apenas uma pequena ferramenta de linha de comando de uma só vez, um método em vez de uma classe deve ser bom. A primeira versão do seu programa pode funcionar com um método, com certeza. Se, mais tarde, você descobrir que acaba com dezenas desses métodos, talvez até com variáveis globais misturadas, é hora de refatorar em classes.
fonte
this
ou explícitoself
) do método. Os métodos de um objeto são mutuamente "abertamente" recursivos; portanto, a substituiçãofoo
faz com que todas asself.foo
chamadas usem essa substituição.Podemos pensar em POO como modelagem do comportamento de um sistema. Observe que o sistema não precisa existir no 'mundo real', embora as metáforas do mundo real às vezes possam ser úteis (por exemplo, "pipelines", "factory" etc.).
Se o nosso sistema desejado for muito complicado para modelar tudo de uma vez, podemos decompô-lo em partes menores e modelá-las (o "domínio do problema"), o que pode envolver uma quebra ainda maior e assim por diante, até que cheguemos a peças cujo comportamento coincide (mais ou menos) o de algum objeto de linguagem embutido, como um número, uma string, uma lista etc.
Quando tivermos essas peças simples, podemos combiná-las para descrever o comportamento de peças maiores, as quais podemos combinar em peças ainda maiores, e assim por diante até podermos descrever todos os componentes do domínio necessários para um todo sistema.
É nessa fase de "combinação" que podemos escrever algumas aulas. Nós escrevemos classes quando não há um objeto existente que se comporte da maneira que queremos. Por exemplo, nosso domínio pode conter "foos", coleções de foos chamadas "bars" e coleções de barras chamadas "bazs". Podemos notar que os foos são simples o suficiente para modelar com strings, então fazemos isso. Nós descobrimos que as barras exigem que seu conteúdo obedeça a alguma restrição específica que não corresponde a nada que o Python fornece; nesse caso, podemos escrever uma nova classe para impor essa restrição. Talvez os bazs não tenham essas peculiaridades, então podemos representá-los com uma lista.
Observe que poderíamos escrever uma nova classe para cada um desses componentes (foos, bars e bazs), mas não precisamos se já houver algo com o comportamento correto. Em particular, para que uma classe seja útil, ela precisa 'fornecer' alguma coisa (dados, métodos, constantes, subclasses etc.), portanto, mesmo que tenhamos muitas camadas de classes personalizadas, devemos usar algum recurso interno; por exemplo, se escrevêssemos uma nova classe para foos, provavelmente ela conteria apenas uma string. Por que não esquecer a classe foo e fazer com que a classe bar contenha essas strings? Lembre-se de que as classes também são um objeto interno, mas são particularmente flexíveis.
Depois de termos nosso modelo de domínio, podemos pegar algumas instâncias particulares dessas partes e organizá-las em uma "simulação" do sistema específico que queremos modelar (por exemplo, "um sistema de aprendizado de máquina para ...").
Uma vez que tenhamos essa simulação, podemos executá-la e, e pronto, temos um (simulação de a) sistema de aprendizado de máquina para ... (ou qualquer outra coisa que estávamos modelando).
Agora, em sua situação específica, você está tentando modelar o comportamento de um componente "extrator de recursos". A questão é: existem objetos embutidos que se comportam como um "extrator de recursos" ou será necessário dividi-lo em coisas mais simples? Parece que os extratores de recursos se comportam muito como objetos de função, então acho que você pode usá-los como modelo.
Uma coisa a ter em mente ao aprender sobre esses tipos de conceitos é que linguagens diferentes podem fornecer diferentes recursos e objetos internos (e, é claro, alguns nem usam terminologia como "objetos"!). Portanto, soluções que fazem sentido em um idioma podem ser menos úteis em outro (isso pode até se aplicar a diferentes versões do mesmo idioma!).
Historicamente, grande parte da literatura OOP (especialmente "padrões de design") se concentrou em Java, que é bem diferente do Python. Por exemplo, classes Java não são objetos, Java não possuía objetos de função até muito recentemente, Java possui verificação estrita de tipo (o que incentiva interfaces e subclasses), enquanto Python incentiva a digitação de patos, Java não possui objetos de módulo, números inteiros Java / carros alegóricos / etc. não são objetos, a metaprogramação / introspecção em Java requer "reflexão" e assim por diante.
Não estou tentando entender o Java (como outro exemplo, muita teoria de OOP gira em torno do Smalltalk, que é muito diferente do Python), apenas estou tentando ressaltar que devemos pensar com muito cuidado sobre o contexto e restrições nas quais as soluções foram desenvolvidas e se isso corresponde à situação em que estamos.
No seu caso, um objeto de função parece ser uma boa escolha. Se você está se perguntando por que algumas diretrizes de "melhores práticas" não mencionam objetos de função como uma possível solução, pode ser simplesmente porque essas diretrizes foram escritas para versões antigas do Java!
fonte
Pragmaticamente falando, quando eu tenho uma "coisa diversa que faz algo importante e deve ser separado", e não tem uma casa clara, eu a coloco em uma
Utilities
seção e a uso como minha convenção de nomenclatura. ieFeatureExtractionUtility
.Esqueça o número de métodos em uma classe; hoje, um único método pode precisar aumentar para cinco métodos amanhã. O que importa é uma estrutura organizacional clara e consistente, como uma área de utilitários para coleções diversas de funções.
fonte