¿Son las clases con un solo método (público) un problema?

45

Actualmente estoy trabajando en un proyecto de software que realiza la compresión y la indexación de las secuencias de video vigilancia. La compresión funciona dividiendo los objetos de fondo y de primer plano, luego guardando el fondo como una imagen estática y el primer plano como un sprite.

Recientemente, me he embarcado en revisar algunas de las clases que he diseñado para el proyecto.

Noté que hay muchas clases que solo tienen un único método público. Algunas de estas clases son:

  • VideoCompressor (con un método compress que toma un video de entrada de tipo RawVideo y devuelve un video de salida de tipo CompressedVideo ).
  • VideoSplitter (con un método split que toma un video de entrada de tipo RawVideo y devuelve un vector de 2 videos de salida, cada uno de tipo RawVideo ).
  • VideoIndexer (con un método index que toma un video de entrada de tipo RawVideo y devuelve un índice de video de tipo VideoIndex ).

Me encuentro creando instancias de cada clase solo para hacer llamadas como VideoCompressor.compress(...) , VideoSplitter.split(...) , VideoIndexer.index(...) .

En la superficie, creo que los nombres de clase son lo suficientemente descriptivos de su función deseada, y en realidad son sustantivos. En correspondencia, sus métodos también son verbos.

¿Es esto realmente un problema?

    
pregunta yjwong 29.01.2014 - 08:16

9 respuestas

78

No, esto no es un problema, todo lo contrario. Es un signo de modularidad y clara responsabilidad de la clase. La interfaz magra es fácil de entender desde el punto de vista de un usuario de esa clase y fomentará el acoplamiento suelto. Esto tiene muchas ventajas pero casi ningún inconveniente. ¡Deseo que se diseñen más componentes de esa manera!

    
respondido por el Doc Brown 29.01.2014 - 08:37
24

Ya no está orientado a objetos. Debido a que esas clases no representan nada, son solo recipientes para las funciones.

Eso no significa que esté mal. Si la funcionalidad es lo suficientemente compleja o cuando es genérica (es decir, los argumentos son interfaces, no tipos finales concretos), tiene sentido colocar esa funcionalidad en módulo separado.

De ahí depende tu idioma. Si el lenguaje tiene funciones libres, deberían ser módulos que exporten funciones. ¿Por qué pretender que es una clase cuando no lo es? Si el idioma no tiene funciones libres como por ejemplo Java, entonces creas clases con un solo método público. Bueno, eso solo muestra los límites del diseño orientado a objetos. A veces, funcional es simplemente mejor coincidencia.

Hay un caso en el que puede necesitar una clase con un solo método público porque tiene que implementar una interfaz con un solo método público. Ya sea para el patrón de observador o la inyección de dependencia o lo que sea. Aquí vuelve a depender del idioma. En los idiomas que tienen funtores de primera clase (C ++ ( std::function o parámetro de plantilla), C # (delegado), Python, Perl, Ruby ( proc ), Lisp, Haskell, ...) estos patrones utilizan tipos de función y don ' t necesita clases Java no tiene (aún, en la versión 8) tipos de funciones, por lo que utiliza interfaces de un solo método y las correspondientes clases de un solo método.

Por supuesto que no estoy abogando por escribir solo una función enorme. Debe tener subrutinas privadas, pero pueden ser privadas para el archivo de implementación (espacio de nombres estático o anónimo a nivel de archivo en C ++) o en una clase auxiliar privada que solo se crea una instancia dentro de la función pública ( ¿Para almacenar datos o no? ).

    
respondido por el Jan Hudec 29.01.2014 - 13:25
13

Puede haber razones para extraer un método dado en una clase dedicada. Una de esas razones es permitir la inyección de dependencia.

Imagina que tienes una clase llamada VideoExporter que, eventualmente, debería poder comprimir un video. Una forma limpia sería tener una interfaz:

interface IVideoCompressor
{
    Stream compress(Video video);
}

que se implementaría así:

class MpegVideoCompressor : IVideoCompressor
{
    // ...
}

class FlashVideoCompressor : IVideoCompressor
{
    // ...
}

y usado de esta manera:

class VideoExporter
{
    // ...
    void export(Destination destination, IVideoCompressor compressor)
    {
        // ...
        destination = compressor(this.source);
        // ...
    }
    // ...
}
Una alternativa mala sería tener un VideoExporter que tenga muchos métodos públicos y haga todo el trabajo, incluida la compresión. Se convertiría rápidamente en una pesadilla de mantenimiento, lo que dificultaría agregar soporte para otros formatos de video.

    
respondido por el Arseni Mourzenko 29.01.2014 - 08:39
6

Este es un signo de que desea pasar funciones como argumentos a otras funciones. Supongo que tu lenguaje (¿Java?) No lo admite; Si ese es el caso, no es tanto una falla en su diseño como una deficiencia en el idioma de su elección. Este es uno de los mayores problemas con los idiomas que insisten en que todo debe ser una clase.

Si en realidad no estás pasando estas funciones falsas, solo quieres una función estática / gratuita.

    
respondido por el Doval 29.01.2014 - 20:02
5

Sé que llego tarde a la fiesta, pero como todos parecen haberse perdido al señalar esto:

Este es un patrón de diseño muy conocido llamado: Patrón de estrategia .

El patrón de estrategia se usa cuando hay varias estrategias posibles para resolver un subproblema. Por lo general, define una interfaz que hace cumplir un contrato en todas las implementaciones y luego utiliza alguna forma de Inyección de dependencia para proporcionar la estrategia concreta para tú.

Por ejemplo, en este caso, podría tener interface VideoCompressor y luego tener varias implementaciones alternativas, por ejemplo, class H264Compressor implements VideoCompressor y class XVidCompressor implements VideoCompressor . No está claro por OP que haya una interfaz involucrada, incluso si no lo está, puede ser simplemente que el autor original dejó la puerta abierta para implementar un patrón de estrategia si es necesario. Lo que en sí mismo es un buen diseño también.

El problema de que OP se encuentra constantemente creando instancias de las clases para llamar a un método es un problema porque no utiliza la inyección de dependencia y el patrón de estrategia correctamente. En lugar de instanciarlo donde lo necesite, la clase que lo contiene debe tener un miembro con el objeto de estrategia. Y este miembro debe ser inyectado, por ejemplo en el constructor.

En muchos casos, el patrón de estrategia da como resultado clases de interfaz (como se muestra) con un solo método doStuff(...) .

    
respondido por el Emily L. 19.02.2016 - 16:17
1
public interface IVideoProcessor
{
   void Split();

   void Compress();

   void Index();
}

Lo que tienes es modular y eso es bueno, pero si agruparas estas responsabilidades en IVideoProcessor, eso probablemente tendría más sentido desde el punto de vista de DDD.

Por otro lado, si la división, la compresión y la indexación no estuvieran relacionadas de alguna manera, entonces las mantendría como componentes separados.

    
respondido por el CodeART 30.01.2014 - 10:55
0

Es un problema: está trabajando desde el aspecto funcional del diseño, en lugar de los datos. Lo que realmente tiene son 3 funciones independientes que han sido OO-ified.

Por ejemplo, tienes una clase VideoCompressor. ¿Por qué trabaja con una clase diseñada para comprimir video? ¿Por qué no tiene una clase de Video con métodos para comprimir los datos (video) que contiene cada objeto de este tipo?

Al diseñar sistemas OO, es mejor crear clases que representen objetos, en lugar de clases que representan actividades que puede aplicar. En los viejos tiempos, las clases se denominaban tipos: OO era una forma de extender un lenguaje con soporte para nuevos tipos de datos. Si piensas en OO de esta manera, obtendrás una mejor manera de diseñar tus clases.

EDITAR:

déjame intentar explicarme un poco mejor, imagina una clase de cadena que tenga un método concat. Puede implementar una cosa en la que cada objeto instanciado de la clase contenga los datos de la cadena, por lo que puede decir

string mystring("Hello"); 
mystring.concat("World");

pero el OP quiere que funcione así:

string mystring();
string result = mystring.concat("Hello", "World");

ahora hay lugares donde se puede usar una clase para mantener una colección de funciones relacionadas, pero eso no es OO, es una forma práctica de usar las funciones OO de un idioma para ayudar a administrar mejor la base de código, pero no es así forma cualquier tipo de "Diseño OO". El objeto en tales casos es totalmente artificial, simplemente se usa así porque el lenguaje no ofrece nada mejor para manejar este tipo de problema. p.ej. En lenguajes como C #, usaría una clase estática para proporcionar esta funcionalidad: reutiliza el mecanismo de la clase, pero ya no necesita crear una instancia de un objeto solo para llamar a los métodos. Terminas con métodos como string.IsNullOrEmpty(mystring) que creo que es pobre en comparación con mystring.isNullOrEmpty() .

Entonces, si alguien pregunta "cómo diseño mis clases", recomiendo pensar en los datos que contendrá la clase en lugar de las funciones que contiene. Si eliges "una clase es un montón de métodos", entonces terminas escribiendo el código de estilo "mejor C". (lo que no es necesariamente malo si está mejorando el código C) pero no le dará el mejor programa diseñado para OO.

    
respondido por el gbjbaanb 29.01.2014 - 12:10
-1

El ISP (principio de segregación de interfaz) dice que ningún cliente debe ser obligado a depender de los métodos que no utiliza. Los beneficios son múltiples y claros. Su enfoque respeta totalmente al ISP, y eso es bueno.

Un enfoque diferente que también respeta al ISP es, por ejemplo, crear una interfaz para cada método (o conjunto de métodos con una alta cohesión) y luego tener una sola clase que implemente todas esas interfaces. Si esto es o no una mejor solución depende del escenario. El beneficio de esto es que, al utilizar la inyección de dependencia, podría tener un cliente con diferentes colaboradores (uno por cada interfaz) pero al final todos los colaboradores apuntarán a la misma instancia de objeto.

Por cierto, dijiste

  

Me encuentro instanciando cada clase

, estas clases parecen ser servicios (y, por lo tanto, sin estado). ¿Has pensado en hacerlos singletons?

    
respondido por el diegomtassis 29.01.2014 - 15:21
-1

Sí, hay un problema. Pero no es grave. Quiero decir que puedes estructurar tu código de esta manera y no pasará nada malo, es mantenible. Pero hay algunas debilidades en ese tipo de estructuración. Por ejemplo, considere si la representación de su video (en su caso se agrupa en la clase RawVideo) cambia, deberá actualizar todas sus clases de operación. O considere que puede tener varias representaciones de video que varían en tiempo de ejecución. Entonces tendrá que hacer coincidir la representación con una clase de "operación" particular. También es subjetivamente molesto arrastrar alrededor de una dependencia para cada operación que desee hacer en algo. y para actualizar la lista de dependencias que se pasan cada vez que decide que ya no es necesaria la operación o que necesita una nueva.

También eso es en realidad una violación de SRP. Algunas personas simplemente tratan el SRP como una guía para dividir responsabilidades (y lo llevan al extremo al tratar cada operación como una responsabilidad distinta) pero olvidan que el SRP también es una guía para agrupar responsabilidades. Y de acuerdo con los SRP, las responsabilidades que cambian por la misma razón se deben agrupar para que, si se produce un cambio, se ubique en la menor cantidad posible de clases / módulos. En cuanto a las clases grandes, no es un problema tener múltiples algoritmos en la misma clase siempre que esos algoritmos estén relacionados (es decir, compartan algunos conocimientos que no deberían ser conocidos fuera de esa clase). El problema son las clases grandes que tienen algoritmos que no están relacionados de ninguna manera y cambian / varían por diferentes razones.

    
respondido por el ddiukariev 14.12.2017 - 17:15

Lea otras preguntas en las etiquetas