Classe que não representa nada - está correto?

42

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

  1. FeatureExtractor
  2. DataSet
  3. 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
Alicja Głowacka
fonte
13
Isso parece perfeitamente bem como uma função. Considerando as três coisas listadas como módulos , não há problema e você pode colocá-las em arquivos diferentes, mas isso não significa que elas precisam ser classes.
Bergi
31
Esteja ciente de que é bastante comum e aceitável usar abordagens não OO no Python.
jpmc26
13
Você pode estar interessado em Design Orientado a Domínio. As "classes devem representar objetos do mundo real" são na verdade falsas ... elas devem representar objetos no domínio . O domínio geralmente está fortemente vinculado ao mundo real, mas, dependendo do aplicativo, algumas coisas podem ou não ser consideradas objetos, ou algumas coisas que "na realidade" são separadas podem acabar vinculadas ou idênticas dentro do domínio do aplicativo.
Bakuriu 16/04/19
1
À medida que você se familiariza com o POO, acho que você descobrirá que as classes raramente correspondem individualmente às entidades do mundo real. Por exemplo, aqui está um ensaio que argumenta que tentar empinar todas as funcionalidades associadas a uma entidade do mundo real em uma única classe é frequentemente um antipadrão: programmer.97things.oreilly.com/wiki/index.php/…
Kevin Reintegrar Monica
1
"eles devem representar objetos reais com os quais trabalhamos". não necessariamente. Muitas linguagens têm uma classe de fluxo que representa um fluxo de bytes, que é um conceito abstrato e não um 'objeto real'. Tecnicamente, um sistema de arquivos também não é um 'objeto real', é apenas um conceito, mas às vezes há classes que representam um sistema de arquivos ou uma parte dele.
Pharap

Respostas:

96

As aulas devem fazer uma coisa e fazê-lo bem

Sim, essa geralmente é uma boa abordagem.

mas, por outro lado, eles devem representar um objeto real com o qual trabalhamos.

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úblicoextract_features() , eu ficaria surpreso se também não contenha muitos métodos privados e talvez algum estado compartilhado. Portanto, ter uma aula FeatureExtractorintroduzirá 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 FeatureExtractorpara 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".

Doc Brown
fonte
33
Eu particularmente gosto que você mencionou a questão do estado interno. Costumo achar que esse é o fator decisivo para tornar uma classe ou função em Python.
David Z
17
Você parece recomendar "classite". Não faça uma classe para isso, é um antipadrão no estilo Java. Se é uma função, faça disso uma função. O estado interno é irrelevante: as funções podem ter isso (através de fechamentos).
Konrad Rudolph
25
@ KonradRudolph: você parece ter perdido que não se trata de fazer uma função. Trata-se de um pedaço de código que requer várias funções, um nome comum e talvez algum estado compartilhado. O uso de um módulo para isso pode ser sensato em linguagens com um conceito de módulo separado das classes.
Doc Brown
8
@DocBrown eu vou ter que discordar. É uma classe com um método público. Em termos de API, isso é indistinguível de uma função. Veja minha resposta para detalhes. O uso de classes de método único pode ser justificado se você precisar criar um tipo específico para representar um estado. Mas esse não é o caso aqui (e mesmo assim, as funções às vezes podem ser usadas).
Konrad Rudolph
6
@DocBrown Estou começando a entender o que você quer dizer: você está falando sobre as funções que são chamadas de dentro 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 com extract_features. (Dito isto, você poderia naturalmente declará-los localmente dentro dessa função.)
Konrad Rudolph
44

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 intou stringcorresponde 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:

E se extract_features () ficaria assim: vale a pena criar uma classe especial para armazenar esse método?

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.

Konrad Rudolph
fonte
Seria útil acrescentar que, se as funções chamadas no exemplo forem de fato funções privadas, encapsulá-las em um módulo ou classe seria inteiramente apropriado. Caso contrário, eu concordo inteiramente.
Leliel
11
Também é útil lembrar que "OOP" não significa "escrever várias classes". Funções são objetos em Python, portanto, não há necessidade de considerar isso como "not OOP". Em vez disso, é simplesmente reutilizar os tipos / classes integrados e "reutilizar" é um dos santos grails na programação. Envolvendo esta em uma classe seria impedindo a reutilização, uma vez que nada seria compatível com esta nova API (a menos que __call__seja definido, caso em que apenas usar uma função!)
Warbo
3
Também sobre o assunto de "classes vs. funções independentes": eev.ee/blog/2013/03/03/…
Joker_vD 16/04/19
1
Não é o caso, embora no Python as "funções livres" também sejam objetos com um tipo que expõe um método __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.
JimmyJames
1
@JimmyJames Right. O ponto principal é que eles oferecem a mesma funcionalidade para a finalidade específica, mas são mais simples de usar.
Konrad Rudolph
36

Estou apenas projetando meu aplicativo e não tenho certeza se entendi corretamente o SOLID e o OOP.

Há mais de 20 anos, e também não tenho certeza.

As aulas devem fazer uma coisa e fazê-lo bem

Difícil de dar errado aqui.

eles devem representar objetos reais com os quais trabalhamos.

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:

Pescador mantém 10 peixes suspensos em uma corda

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:

cartas festivas suspensas de uma corda diz "LETS FAÇA AS COISAS!"

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.

candied_orange
fonte
1
Uma corda bonita das fotos ...
Zev Spitz
Neste ponto, não tenho certeza de que "string" seja metafórico. Ele só tem um determinado significado no domínio da programação, como fazer palavras como classe tablee column...
Kyralessa
@ Kyralessa, você éter ensina a metáfora ao novato ou deixa que seja mágico para eles. Por favor, salve-me de programadores que acreditam em mágica.
Candied_orange
6

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.

JacquesB
fonte
+1 para "não há necessidade de criar uma classe se você puder resolvê-la com uma função". Às vezes alguém precisa falar a verdade.
Tchrist 17/04/19
5

É correto criar classes que não representam uma coisa, mas fazem uma coisa?

Em geral, tudo bem.

Sem uma descrição um pouco mais específica, o que a FeatureExtractorclasse deve fazer exatamente, é difícil dizer.

De qualquer forma, mesmo que FeatureExtractorexponha apenas uma extract_features()função pública , eu poderia pensar em configurá-la com uma Strategyclasse, 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.

E se extract_features () ficaria assim: vale a pena criar uma classe especial para armazenar esse método?

A linha

 sent = SentimentAnalyser()

compreende exatamente o que eu quis dizer que você pode configurar uma classe com uma estratégia .

Se você tiver uma interface para essa SentimentAnalyserclasse, poderá passá-la para a FeatureExtractorclasse em seu ponto de construção, em vez de se acoplar diretamente a essa implementação específica em sua função.

πάντα ῥεῖ
fonte
2
Não vejo uma razão para adicionar complexidade (uma FeatureExtractorclasse) apenas para introduzir ainda mais complexidade (uma interface para a SentimentAnalyserclasse). Se a dissociação for desejável, então a função extract_featurespode ser aceita get_sentimentcomo argumento (a loadchamada parece ser independente da função e exigida apenas por seus efeitos). Observe também que o Python não possui / incentiva interfaces.
Warbo
1
@warbo - mesmo que você forneça a função como argumento, ao transformá-la em uma função, você restringirá as implementações em potencial daquelas que caberão no formato de uma função, mas se houver a necessidade de gerenciar o estado persistente entre uma chamada e o Em seguida (por exemplo, a CacheingFeatureExtractorou a TimeSeriesDependentFeatureExtractor), um objeto seria um ajuste muito melhor. Só porque não há necessidade de um objeto atualmente não significa que nunca haverá.
Jules
3
@Jules Em primeiro lugar, você não precisará disso (YAGNI), em segundo lugar, as funções Python podem fazer referência a estados persistentes (fechamentos) se você precisar (você não vai), em terceiro lugar, usar uma função não restringe nada, já que qualquer objeto com um __call__método será compatível se você precisar (você não vai), em quarto lugar, adicionando um invólucro como FeatureExtractorse 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 )
Warbo 16/04/19
0

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.

É correto criar classes que não representam uma coisa, mas fazem uma coisa?

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.

E se extract_features () ficaria assim: vale a pena criar uma classe especial para armazenar esse método?

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.

AnoE
fonte
3
Observe que em cenários como esse, chamar procedimentos independentes de "métodos" pode ser confuso. A maioria das linguagens, incluindo Python, as chama de "funções". "Métodos" são funções / procedimentos que são obrigados a uma instância específica de uma classe, que é o oposto de seu uso do termo :)
Warbo
É verdade, @Warbo. Ou podemos chamá-los de procedure ou defun ou sub ou ...; e eles podem ser métodos de classe (sic) não relacionados a uma instância. Espero que o leitor gentil seja capaz de abstrair o significado pretendido. :)
AnoE 16/04
@Warbo isso é bom saber! A maioria dos materiais de aprendizagem que encontrei afirma que os termos função e método são intercambiáveis ​​e que é apenas uma preferência dependente do idioma.
Dom Dom
@ Dom Em geral, uma função ("pura") "é um mapeamento dos valores de entrada para os valores de saída; um "procedimento" é uma função que também pode causar efeitos (por exemplo, excluir um arquivo); ambos são enviados estaticamente (ou seja, consultados no escopo lexical). Um "método" é uma função ou procedimento (geralmente) despachado dinamicamente (procurado) a partir de um valor (chamado "objeto"), que é automaticamente vinculado a um argumento (implícito thisou explícito self) do método. Os métodos de um objeto são mutuamente "abertamente" recursivos; portanto, a substituição foofaz com que todas as self.foochamadas usem essa substituição.
Warbo
0

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!

Warbo
fonte
0

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 Utilitiesseção e a uso como minha convenção de nomenclatura. ie FeatureExtractionUtility.

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.

Dom
fonte