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

42

Estou apenas projetando meu aplicativo e não tenho certeza se entendi SOLID e OOP corretamente. As classes devem fazer uma coisa e fazê-lo bem, mas por outro lado elas 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 faz dela 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 é importante, mas eu estou usando o Python

E se extract_features () tiver esta aparência: vale a pena criar uma classe especial para manter 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
    
por Alicja Głowacka 15.04.2018 / 13:36
fonte

8 respostas

96

Classes should do 1 thing and do it well

Sim, isso geralmente é uma boa abordagem.

but from the other hand they should represent real object we work with.

Não, isso é um mal-entendido comum da IMHO. O acesso de um bom iniciante ao OOP é freqüentemente "começar com objetos que representam coisas do mundo real" , isso é verdade.

No entanto, você não deve parar com isso !

As classes 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 "extractor de funcionalidades" é provavelmente um desses módulos, e mesmo que contenha apenas um método público extract_features() , ficaria espantado se também não contivesse muitos métodos privados e talvez algum estado partilhado . Portanto, ter uma classe FeatureExtractor introduzirá um local natural para esses métodos privados.

Nota: em linguagens como o Python, que suportam um conceito de módulo separado, também é possível usar um módulo FeatureExtractor para isso, mas no contexto dessa questão, isso é uma diferença insignificante para a IMHO.

Além disso, um "extrator de recursos" pode ser imaginado como "uma pessoa ou bot que extrai recursos". Essa é uma "coisa" abstrata, talvez não uma coisa que você possa encontrar no mundo real, mas o próprio nome é uma abstração útil, que dá a todos uma noção de qual é a responsabilidade dessa classe. Então eu discordo que essa classe não "representa nada".

    
por 15.04.2018 / 14:35
fonte
43

Doc Brown é impecável: as turmas não precisam representar objetos do mundo real. Eles só precisam ser úteis . Classes são fundamentalmente apenas tipos adicionais, e a que int ou string corresponde no mundo real? São descrições abstratas, coisas não concretas e tangíveis.

Dito isto, o seu caso é especial. De acordo com sua descrição:

And if extract_features() would look like that: is it worth to create a special class to hold that method?

Você tem toda a razão: se seu código é o exibido, não adianta transformá-lo em uma turma. Há uma famosa palestra que argumenta que tais usos de classes em Python são cheiro de código e que funções simples são geralmente suficientes. Seu caso é um exemplo perfeito disso.

O uso excessivo de classes se deve ao fato de que a OOP se tornou mainstream com Java nos anos 90. Infelizmente, Java na época não tinha vários recursos de linguagem modernos (como closures), o que significa que muitos conceitos eram difíceis ou impossíveis de expressar sem o uso de classes. Por exemplo, em Java, até recentemente, era impossível ter métodos que transportassem estado (isto é, closures). Em vez disso, você tinha que escrever uma classe para transportar o estado e que expunha um único método (chamado algo como invoke ).

Infelizmente, esse estilo de programação se tornou popular muito além do Java (em parte devido a um influente livro de engenharia de software que, de outra forma, é muito útil) ), mesmo em idiomas que não exigem tais soluções alternativas.

No Python, as classes são obviamente uma ferramenta muito importante e devem ser usadas livremente. Mas eles não são a única ferramenta, e não há razão para usá-los onde eles não fazem sentido. É um equívoco comum que funções livres não tenham lugar na OOP.

    
por 15.04.2018 / 21:48
fonte
35

I am just designing my application and I am not sure if I understand SOLID and OOP correctly.

Já fiz isso há mais de 20 anos e também não tenho certeza.

Classes should do 1 thing and do it well

Difícil de dar errado aqui.

they should represent real objects we work with.

Ah, é mesmo? Deixe-me apresentar a classe mais popular e bem-sucedida de todos os tempos: String . Nós usamos para texto. E o objeto do mundo real que representa é este:

Porquenão,nemtodososprogramadoresestãoobcecadoscomapesca.Aquiestamosusandoalgochamadometáfora.Nãoháproblemaemfazermodelosdecoisasquerealmentenãoexistem.Éaideiaquedeveserclara.Vocêestácriandoimagensnamentedosseusleitores.Essasimagensnãoprecisamserreais.Apenasentendicomfacilidade.

UmbomprojetoOOPagrupamensagens(métodos)emtornodedados(estado)paraqueasreaçõesaessasmensagenspossamvariardependendodosdados.Sefazerissomodelaalgumacoisadomundoreal,spiffy.Senão,tudobem.Desdequefaçasentidoparaoleitor,tudobem.

Agoracomcerteza,vocêpodepensarassim:

festivoletrassuspensasdeumastring"LETS DO THINGS!"

mas se você acha que isso tem que existir no mundo real antes que você possa fazer uso da metáfora, bem, sua carreira de programação envolverá muitas artes e ofícios.

    
por 16.04.2018 / 11:21
fonte
6

Cuidado! Em nenhum lugar SOLID diz que uma classe deve "fazer uma coisa". Se esse fosse o caso, as classes só teriam um único método, e não haveria realmente uma diferença entre classes e funções.

O SOLID diz que uma classe deve representar uma única responsabilidade . Estas são como as responsabilidades das pessoas em uma equipe: o motorista, o advogado, o batedor de carteira, o designer gráfico, etc. Cada uma dessas pessoas pode executar várias tarefas (relacionadas), mas todas relacionadas a uma única responsabilidade.

O ponto disto é - se houver uma mudança nos requisitos, você idealmente só precisa modificar uma única classe. Isso apenas torna o código mais fácil de entender, mais fácil de modificar e reduz o risco.

Não há regra de que um objeto deva representar "uma coisa real". Esta é apenas uma tradição de cultura de carga desde que a OO foi inicialmente inventada para o uso em simulações. Mas o seu programa não é uma simulação (poucas aplicações OO modernas), portanto esta regra não se aplica. Desde que cada turma tenha uma responsabilidade bem definida, você deve estar bem.

Se uma classe realmente tem apenas um método e , a classe não tem nenhum estado, você pode considerar uma função autônoma. Isto é certamente bom e segue os princípios KISS e YAGNI - não há necessidade de fazer uma aula se você puder resolvê-la com uma função. Por outro lado, se você tem motivos para acreditar que precisa de um estado interno ou de várias implementações, é melhor criar uma classe inicial. Você terá que usar seu melhor julgamento aqui.

    
por 16.04.2018 / 12:51
fonte
5

Is it correct to create classes that do not represent one thing but do one thing?

Em geral, tudo bem.

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

De qualquer forma, mesmo que o FeatureExtractor exponha apenas uma função extract_features() pública, posso pensar em configurá-lo com um Strategy classe, que determina exatamente como a extração deve ser feita.

Outro exemplo é uma classe com uma função de modelo .

E há mais Padrões de design comportamental , que são baseados em modelos de classe.

Como você adicionou algum código para esclarecimento.

And if extract_features() would look like that: is it worth to create a special class to hold that method?

A linha

 sent = SentimentAnalyser()

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

Se você tiver uma interface para essa classe SentimentAnalyser , poderá transmiti-la à classe FeatureExtractor em seu ponto de construção, em vez de acoplá-la diretamente a essa implementação específica em sua função.

    
por 15.04.2018 / 14:07
fonte
0

Padrões e toda a linguagem / conceitos extravagantes à parte: o que você encontrou foi um trabalho ou um processo em lote .

No final do dia, até mesmo um programa OOP puro precisa de alguma forma ser impulsionado por algo, para realmente realizar o trabalho; deve haver um ponto de entrada de alguma forma. No padrão MVC, por exemplo, o ontroller "C" recebe eventos de cliques etc. da GUI e, em seguida, orquestra os outros componentes. Em ferramentas clássicas de linha de comando, uma função "main" faria o mesmo.

Is it correct to create classes that do not represent one thing but do one thing?

Sua classe representa uma entidade que faz algo e orquestra todo o resto. Você pode nomear Controller , Job , Main ou o que vier à mente.

And if extract_features() would look like that: is it worth to create a special class to hold that method?

Isso depende das circunstâncias (e eu não estou familiarizado com a maneira usual como isso é feito em Python). Se esta é apenas uma pequena ferramenta de linha de comando, então um método em vez de uma classe deve estar bem. A primeira versão do seu programa pode usar um método, com certeza. Se, mais tarde, você descobrir que acaba com dezenas desses métodos, talvez até mesmo com variáveis globais misturadas, então é hora de refatorar as classes.

    
por 15.04.2018 / 21:17
fonte
0

Podemos pensar em OOP como modelando o comportamento de um sistema. Note que o sistema não tem que existir no 'mundo real', embora metáforas do mundo real às vezes possam ser úteis (por exemplo, "pipelines", "factories", etc.).

Se o nosso sistema desejado é muito complicado para modelar tudo de uma vez, podemos dividi-lo em pedaços menores e modelá-los (o "domínio do problema"), o que pode envolver uma quebra ainda maior, e assim por diante. cujo comportamento corresponde (mais ou menos) a algum objeto de linguagem interno 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, que podemos combinar em partes maiores, e assim por diante, até que possamos descrever todos os componentes do domínio que são necessários para todo um sistema.

É essa fase de "combinar juntos" onde podemos escrever algumas classes. Nós escrevemos classes quando não há um objeto existente que se comporta da maneira que queremos. Por exemplo, nosso domínio pode conter "foos", coleções de foos chamadas "barras" e coleções de barras chamadas "bazs". Podemos notar que foos são simples o suficiente para modelar com strings, então fazemos isso. Descobrimos que as barras exigem que seus conteúdos obedeçam a alguma restrição específica que não corresponde a nada que o Python forneça, caso em que poderíamos escrever uma nova classe para impor essa restrição. Talvez os bazs não tenham tais peculiaridades, então podemos apenas representá-los com uma lista.

Note que nós poderíamos escrever uma nova classe para cada um desses componentes (foos, bars e bazs), mas nós não precisamos se já houver algo com o comportamento correto. Em particular, para uma classe ser útil, ela precisa 'fornecer' algo (dados, métodos, constantes, subclasses, etc.), portanto, mesmo que tenhamos muitas camadas de classes personalizadas, devemos usar eventualmente algum recurso interno; por exemplo, se escrevêssemos uma nova classe para foos, ela provavelmente conteria apenas uma string, então por que não esquecer a classe foo e ter a classe bar contendo essas strings? Tenha em mente que as classes também são um objeto embutido, elas são apenas particularmente flexíveis.

Uma vez que tenhamos nosso modelo de domínio, podemos tomar algumas instâncias dessas peças e organizá-las em uma "simulação" do sistema específico que queremos modelar (por exemplo, "um sistema de aprendizado de máquina". para ... ").

Assim que tivermos essa simulação, podemos executá-la e, pronto, temos um sistema de aprendizado de máquina em funcionamento (simulação de a) para ... (ou qualquer outro modelo que estivéssemos modelando).

Agora, na sua situação particular, você está tentando modelar o comportamento de um componente "extractor de recursos". A questão é, existem objetos embutidos que se comportam como um "extrator de recursos", ou você precisará dividi-los em coisas mais simples? Parece que os extratores de recursos se comportam muito como objetos de função, então eu acho que você estaria bem para usá-los como seu modelo.

Uma coisa a ter em mente ao aprender sobre esses tipos de conceitos é que diferentes linguagens podem fornecer diferentes recursos e objetos internos (e, é claro, alguns nem usam terminologia como "objetos"!). Portanto, as 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, um lote da literatura de OOP (especialmente "padrões de design") tem se concentrado em Java, que é bem diferente do Python. Por exemplo, as classes Java não são objetos, Java não tem objetos de função até muito recentemente, Java possui verificação de tipo estrita (que incentiva interfaces e subclasses) enquanto o Python incentiva a digitação de pato, Java não possui objetos de módulo, inteiros Java flutuadores / etc. não são objetos, meta-programação / introspecção em Java requer "reflexão" e assim por diante.

Eu não estou tentando escolher Java (como outro exemplo, muita teoria de OOP gira em torno de Smalltalk, que é novamente muito diferente do Python), estou apenas tentando mostrar que devemos pensar muito cuidadosamente sobre o contexto e as restrições em que 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 uma diretriz de "melhor prática" não menciona objetos de função como uma possível solução, pode ser simplesmente porque essas diretrizes foram escritas para versões antigas do Java!

    
por 16.04.2018 / 14:55
fonte
0
Pragmaticamente falando, quando eu tenho uma "coisa diversa que faz algo importante e deve ser separada", e não tem uma casa clara, eu coloco em uma seção Utilities e uso isso como minha convenção de nomenclatura. ie. FeatureExtractionUtility .

Esqueça o número de métodos em uma classe; um único método hoje pode precisar crescer para cinco métodos amanhã. O que importa é uma estrutura organizacional clara e consistente, como uma área de utilidades para diversas coleções de funções.

    
por 16.04.2018 / 21:18
fonte