Clase que no representa nada, ¿es correcto?

42

Estoy diseñando mi aplicación y no estoy seguro si entiendo SÓLIDO y OOP correctamente. Las clases deben hacer 1 cosa y hacerlo bien pero, por otro lado, deben representar objetos reales con los que trabajamos.

En mi caso, hago una extracción de características en un conjunto de datos y luego hago un análisis de aprendizaje automático. Supongo que podría crear tres clases

  1. FeatureExtractor
  2. DataSet
  3. Analizador

Pero la clase FeatureExtractor no representa nada, hace algo que hace que sea más una rutina que una clase. Tendrá una sola función que se utilizará: extract_features ()

¿Es correcto crear clases que no representan una cosa sino que hacen una cosa?

EDITAR: no estoy seguro de si es importante pero estoy usando Python

Y si extract_features () se vería así: ¿vale la pena crear una clase especial para mantener ese 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
    
pregunta Alicja Głowacka 15.04.2018 - 13:36

8 respuestas

96
  

Las clases deben hacer 1 cosa y hacerlo bien

Sí, ese es generalmente un buen enfoque.

  

pero, por otro lado, deberían representar un objeto real con el que trabajamos.

No, eso es un malentendido común de IMHO. El acceso de un buen principiante a la POO es a menudo "comenzar con objetos que representan cosas del mundo real" , eso es cierto.

Sin embargo, no debes detenerte con esto !

Las clases pueden (y deben) usarse para estructurar su programa de varias maneras. Modelar objetos del mundo real es un aspecto de esto, pero no el único. Crear módulos o componentes para una tarea específica es otro caso de uso razonable para las clases. Un "extractor de características" es probablemente un módulo de este tipo, e incluso contiene solo un método público extract_features() , me sorprendería si no contuviera muchos métodos privados y quizás algún estado compartido. . Por lo tanto, tener una clase FeatureExtractor introducirá una ubicación natural para estos métodos privados.

Nota al margen: en idiomas como Python que admiten un concepto de módulo separado, también se puede usar un módulo FeatureExtractor para esto, pero en el contexto de esta pregunta, esta es una diferencia insignificante en mi humilde opinión

Por otra parte, un "extractor de características" se puede imaginar como "una persona o un robot que extrae características". Esa es una "cosa" abstracta, tal vez no la encontrará en el mundo real, pero el nombre en sí es una abstracción útil, que le da a todos una idea de cuál es la responsabilidad de esa clase. Así que no estoy de acuerdo en que esta clase no "represente nada".

    
respondido por el Doc Brown 15.04.2018 - 14:35
43

Doc Brown es impecable: las clases no necesitan representar objetos del mundo real. Solo tienen que ser útiles . Las clases son básicamente tipos adicionales, y ¿a qué se corresponden en el mundo real int o string ? Son descripciones abstractas, no cosas concretas, tangibles.

Dicho esto, tu caso es especial. Según su descripción:

  

Y si extract_features () se vería así: ¿vale la pena crear una clase especial para mantener ese método?

Tienes toda la razón: si tu código es como se muestra, no sirve de nada convertirlo en una clase. Hay una una famosa charla que sostiene que tales usos de las clases en Python son olor a código, y que las funciones simples son a menudo suficientes. Tu caso es un ejemplo perfecto de esto.

El uso excesivo de clases se debe al hecho de que OOP se convirtió en la corriente principal de Java en la década de 1990. Desafortunadamente, en ese momento Java carecía de varias características de lenguaje moderno (como los cierres), lo que significa que muchos conceptos eran difíciles o imposibles de expresar sin el uso de clases. Por ejemplo, hasta hace poco era imposible en Java tener métodos que llevaran el estado (es decir, los cierres). En su lugar, tuvo que escribir una clase para llevar el estado, y que expuso un solo método (llamado algo así como invoke ).

Desafortunadamente, este estilo de programación se popularizó mucho más allá de Java (en parte debido a un libro de ingeniería de software influyente que, por lo demás, es muy útil ), incluso en idiomas que no requieren tales soluciones.

En Python, las clases son obviamente una herramienta muy importante y se deben usar generosamente. Pero no son la herramienta only , y no hay razón para usarlas donde no tienen sentido. Es un error común pensar que las funciones libres no tienen lugar en OOP.

    
respondido por el Konrad Rudolph 15.04.2018 - 21:48
35
  

Estoy diseñando mi aplicación y no estoy seguro si entiendo SÓLIDO y OOP correctamente.

He estado en esto por más de 20 años y tampoco estoy seguro.

  

Las clases deben hacer 1 cosa y hacerlo bien

Es difícil equivocarse aquí.

  

deben representar objetos reales con los que trabajamos.

¿En serio? Permítame presentarle la clase más popular y exitosa de todos los tiempos: String . Lo usamos para texto. Y el objeto del mundo real que representa es este:

Porquéno,notodoslosprogramadoresestánobsesionadosconlapesca.Aquíestamosusandoalgollamadometáfora.Estábienhacermodelosdecosasquerealmentenoexisten.Eslaideaquedebequedarclara.Estáscreandoimágenesenlamentedetuslectores.Esasimágenesnotienenqueserreales.Simplementeentendidofácilmente.

UnbuendiseñodeOOPagrupalosmensajes(métodos)alrededordelosdatos(estado),demodoquelasreaccionesaesosmensajespuedenvariardependiendodeesosdatos.Sihaceresomodelaalgodelmundoreal,spiffy.Sino,ohbien.Mientrastengasentidoparaellector,estábien.

Ahora,claro,podríaspensardeestamanera:

festivelasletrassuspendidasdeunacadenaleen"¡HAGAMOS LAS COSAS!"

pero si crees que esto tiene que existir en el mundo real antes de poder utilizar la metáfora, bien, tu carrera en programación incluirá muchas artes y manualidades.

    
respondido por el candied_orange 16.04.2018 - 11:21
6

¡Cuidado! En ninguna parte dice SOLID que una clase solo debe "hacer una cosa". Si ese fuera el caso, las clases solo tendrían un solo método, y realmente no habría una diferencia entre las clases y las funciones.

SOLID dice que una clase debe representar una sola responsabilidad . Estas son como las responsabilidades de las personas en un equipo: el conductor, el abogado, el carterista, el diseñador gráfico, etc. Cada una de estas personas puede realizar múltiples tareas (relacionadas), pero todas relacionadas con una sola responsabilidad.

El punto de esto es: si hay un cambio en los requisitos, lo ideal es que solo necesite modificar una sola clase. Esto solo hace que el código sea más fácil de entender, más fácil de modificar y reduce el riesgo.

No existe una regla que indique que un objeto debe representar "una cosa real". Esto es solo una tradición de culto a la carga, ya que OO fue inicialmente inventado para el uso en simulaciones. Pero su programa no es una simulación (pocas aplicaciones OO modernas), por lo que esta regla no se aplica. Siempre que cada clase tenga una responsabilidad bien definida, debería estar bien.

Si una clase realmente solo tiene un solo método y la clase no tiene ningún estado, puede considerar convertirla en una función independiente. Esto es ciertamente correcto y sigue los principios de KISS y YAGNI: no es necesario hacer una clase si puede resolverlo con una función. Por otro lado, si tiene razones para creer que podría necesitar un estado interno o implementaciones múltiples, también podría convertirlo en una clase por adelantado. Tendrás que usar tu mejor juicio aquí.

    
respondido por el JacquesB 16.04.2018 - 12:51
5
  

¿Es correcto crear clases que no representan una cosa sino que hacen una cosa?

En general eso está bien.

Sin una descripción un poco más específica de lo que se supone que debe hacer exactamente la clase FeatureExtractor , es difícil decirlo.

De todos modos, incluso si FeatureExtractor expone solo una función pública extract_features() , podría pensar en configurarlo con un Strategy clase, que determina cómo se debe realizar exactamente la extracción.

Otro ejemplo es una clase con una función de plantilla .

Y hay más Behavioral Design Patterns , que se basan en modelos de clase.

A medida que agregaste un código para aclarar.

  

Y si extract_features () se vería así: ¿vale la pena crear una clase especial para mantener ese método?

La línea

 sent = SentimentAnalyser()

comprende exactamente lo que quería decir con que podría configurar una clase con una Estrategia .

Si tiene una interfaz para esa clase SentimentAnalyser , puede pasarla a la clase FeatureExtractor en su punto de construcción, en lugar de acoplarse directamente a esa implementación específica en su función.

    
respondido por el πάντα ῥεῖ 15.04.2018 - 14:07
0

Patrones y todo el lenguaje / conceptos sofisticados a un lado: lo que ha encontrado es un Trabajo o un Proceso por lotes .

Al final del día, incluso un programa OOP puro necesita ser impulsado por algo para realizar un trabajo; Debe haber un punto de entrada de alguna manera. En el patrón MVC, por ejemplo, el controlador "C" recibe eventos de clic, etc. de la GUI y luego organiza los otros componentes. En las herramientas clásicas de línea de comando, una función "principal" haría lo mismo.

  

¿Es correcto crear clases que no representan una cosa sino que hacen una cosa?

Su clase representa una entidad que hace algo y organiza todo lo demás. Puede llamarlo Controlador , Trabajo , Principal o lo que se le ocurra.

  

Y si extract_features () se vería así: ¿vale la pena crear una clase especial para mantener ese método?

Eso depende de las circunstancias (y no estoy familiarizado con la forma habitual en que se hace en Python). Si esto es solo una pequeña herramienta de línea de comandos de un solo disparo, entonces un método en lugar de una clase debería estar bien. La primera versión de su programa puede salirse con un método, seguro. Si, más tarde, descubre que termina con docenas de métodos de este tipo, tal vez incluso con variables globales mezcladas, es hora de refactorizar las clases.

    
respondido por el AnoE 15.04.2018 - 21:17
0

Podemos pensar en OOP como modelado el comportamiento de un sistema. Tenga en cuenta que el sistema no tiene que existir en el 'mundo real', aunque las metáforas del mundo real a veces pueden ser útiles (por ejemplo, "tuberías", "fábricas", etc.).

Si nuestro sistema deseado es demasiado complicado para modelarlo todo al mismo tiempo, podemos dividirlo en pedazos más pequeños y modelarlos (el "dominio del problema"), lo que puede implicar descomponerlo más, y así sucesivamente hasta que lleguemos a pedazos cuyo comportamiento coincide (más o menos) con el de algún objeto de lenguaje incorporado como un número, una cadena, una lista, etc.

Una vez que tengamos esas piezas simples, podemos combinarlas para describir el comportamiento de las piezas más grandes, que podemos combinar juntas en piezas aún más grandes, y así sucesivamente hasta que podamos describir todos los componentes del dominio que son necesarios. para todo un sistema.

Es esta fase de "combinación" donde podríamos escribir algunas clases. Escribimos clases cuando no hay un objeto existente que se comporte de la manera que queremos. Por ejemplo, nuestro dominio puede contener "foos", colecciones de foos llamadas "barras" y colecciones de barras llamadas "bazs". Podríamos notar que los foos son lo suficientemente simples para modelar con cuerdas, así que hacemos eso. Descubrimos que las barras requieren que su contenido obedezca a alguna restricción en particular que no coincide con lo que Python proporciona, en cuyo caso podríamos escribir una nueva clase para imponer esta restricción. Tal vez los bazs no tengan tales peculiaridades, por lo que podemos representarlos con una lista.

Tenga en cuenta que podríamos escribir una nueva clase para cada uno de esos componentes (foos, barras y bazs), pero no necesitamos si ya hay algo Con el comportamiento correcto. En particular, para que una clase sea útil, necesita "proporcionar" algo (datos, métodos, constantes, subclases, etc.), por lo que incluso si tenemos muchas capas de clases personalizadas, debemos finalmente usar alguna característica incorporada; por ejemplo, si escribiéramos una nueva clase para foos, probablemente solo contendría una cadena, ¿por qué no olvidar la clase foo y hacer que la clase bar contenga esas cadenas en su lugar? Tenga en cuenta que las clases también son un objeto incorporado, son simplemente uno especialmente flexible.

Una vez que tengamos nuestro modelo de dominio, podemos tomar instancias particulares de esas piezas y organizarlas en una "simulación" del sistema en particular que queremos modelar (por ejemplo, "un sistema de aprendizaje automático para ... ").

Una vez que tengamos esta simulación, podemos ejecutarla y, listo, tenemos un sistema de aprendizaje automático (simulación de a) para ... (o cualquier otra cosa que estemos modelando).

Ahora, en su situación particular, está intentando modelar el comportamiento de un componente "extractor de características". La pregunta es, ¿hay algún objeto incorporado que se comporte como un "extractor de características", o tendrá que dividirlo en cosas más simples? Parece que los extractores de características se comportan de manera muy parecida a los objetos de función, por lo que creo que sería bueno usarlos como modelo.

Una cosa que se debe tener en cuenta al aprender sobre este tipo de conceptos es que los diferentes idiomas pueden proporcionar diferentes características y objetos integrados (y, por supuesto, algunos ni siquiera usan terminología como "objetos"). Por lo tanto, las soluciones que tienen sentido en un idioma podrían ser menos útiles en otro (¡esto incluso puede aplicarse a diferentes versiones del mismo idioma!).

Históricamente, un lote de la literatura de OOP (especialmente "patrones de diseño") se ha centrado en Java, que es bastante diferente de Python. Por ejemplo, las clases de Java no son objetos, Java no tenía objetos de función hasta hace muy poco tiempo, Java tiene una comprobación de tipos estricta (lo que fomenta las interfaces y subclases), mientras que Python fomenta la tipificación de pato, Java no tiene objetos de módulo, enteros Java carrozas / etc no son objetos, la meta-programación / introspección en Java requiere "reflexión", y así sucesivamente.

No estoy tratando de elegir Java (como otro ejemplo, mucha teoría de la POO se basa en Smalltalk, que a su vez es muy diferente de Python), solo estoy tratando de señalar que debemos pensar con mucho cuidado el contexto y las limitaciones en las que se desarrollaron las soluciones, y si esto coincide con la situación en la que nos encontramos.

En su caso, un objeto de función parece ser una buena opción. Si se está preguntando por qué algunas pautas de "mejores prácticas" no mencionan los objetos de función como una posible solución, ¡podría ser simplemente porque esas directrices se escribieron para versiones antiguas de Java!

    
respondido por el Warbo 16.04.2018 - 14:55
0

Hablando de manera pragmática, cuando tengo una "cosa miscelánea que hace algo importante y debería estar separada", y no tiene un hogar claro, la puse en una sección Utilities y la uso como mi convención de nombres. es decir. FeatureExtractionUtility .

Olvídate del número de métodos en una clase; un solo método hoy puede necesitar crecer a cinco métodos mañana. Lo que importa es una estructura organizativa clara y consistente, como un área de utilidades para varias colecciones de funciones.

    
respondido por el Dom 16.04.2018 - 21:18

Lea otras preguntas en las etiquetas