Actualmente estoy trabajando en un proyecto de software que realiza compresión e indexación en imágenes de videovigilancia. La compresión funciona dividiendo los objetos de fondo y 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
compress
método que toma un video de entrada de tipoRawVideo
y devuelve un video de salida de tipoCompressedVideo
). - VideoSplitter (con un
split
método que toma un video de entrada de tipoRawVideo
y devuelve un vector de 2 videos de salida, cada uno de tipoRawVideo
). - VideoIndexer (con un
index
método que toma un video de entrada de tipoRawVideo
y devuelve un índice de video de tipoVideoIndex
).
Me encuentro una instancia de cada clase sólo para hacer llamadas como VideoCompressor.compress(...)
, VideoSplitter.split(...)
, VideoIndexer.index(...)
.
En la superficie, creo que los nombres de clase son suficientemente descriptivos de su función prevista, y en realidad son sustantivos. En consecuencia, sus métodos también son verbos.
¿Es esto realmente un problema?
fuente
Respuestas:
No, esto no es un problema, todo lo contrario. Es un signo de modularidad y una clara responsabilidad de la clase. La interfaz esbelta es fácil de entender desde el punto de vista de un usuario de esa clase, y fomentará el acoplamiento flojo. Esto tiene muchas ventajas pero casi ningún inconveniente. ¡Deseo que más componentes se diseñen de esa manera!
fuente
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 suficientemente compleja o cuando es genérica (es decir, los argumentos son interfaces, no tipos finales concretos), tiene sentido colocar esa funcionalidad en un módulo separado .
A partir de ahí depende de 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 lenguaje no tiene funciones libres como, por ejemplo, Java, entonces crea clases con un único método público. Bueno, eso solo muestra los límites del diseño orientado a objetos. A veces funcional es simplemente una mejor combinación.
Hay un caso en el que puede necesitar una clase con un método público único porque tiene que implementar una interfaz con un método público único. Ya sea por patrón de observación o inyección de dependencia o lo que sea. Aquí nuevamente depende del idioma. En lenguajes que tienen functores de primera clase (C ++ (
std::function
o parámetro de plantilla), C # (delegado), Python, Perl, Ruby (proc
), Lisp, Haskell, ...) estos patrones usan tipos de función y no necesitan clases. Java no tiene (aún, tendrá en la versión 8) tipos de funciones, por lo que utiliza interfaces de método único y las clases de método único correspondientes.Por supuesto, no estoy abogando por escribir una sola 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 instancia dentro de la función pública (¿ Para almacenar datos o no? ).
fuente
Puede haber razones para extraer un método dado en una clase dedicada. Una de esas razones es permitir la inyección de dependencia.
Imagine que tiene una clase llamada
VideoExporter
que, eventualmente, debería poder comprimir un video. Una forma limpia sería tener una interfaz:que se implementaría así:
y usado así:
Una mala alternativa sería tener uno
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 dificulta agregar soporte para otros formatos de video.fuente
Esta es una señal de que desea pasar funciones como argumentos a otras funciones. Supongo que su lenguaje (¿Java?) No lo admite; Si ese es el caso, no se trata tanto de una falla en su diseño como de 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 no estás pasando estas funciones falsas, entonces solo quieres una función libre / estática.
fuente
Sé que llego tarde a la fiesta, pero como todos parecen haber dejado de señalar esto:
Este es un patrón de diseño bien 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 usa alguna forma de Inyección de dependencias para proporcionar la estrategia concreta para usted.
Por ejemplo, en este caso, podría tener
interface VideoCompressor
y luego tener varias implementaciones alternativas, por ejemplo,class H264Compressor implements VideoCompressor
yclass XVidCompressor implements VideoCompressor
. Desde el OP no está claro que haya una interfaz involucrada, incluso si no la hay, puede ser simplemente que el autor original dejó la puerta abierta para implementar el patrón de estrategia si es necesario. Lo cual en sí mismo es un buen diseño también.El problema con el que OP se encuentra constantemente creando instancias de las clases para llamar a un método es un problema con ella que no usa la inyección de dependencia y el patrón de estrategia correctamente. En lugar de instanciarlo donde lo necesita, la clase que lo contiene debe tener un miembro con el objeto de estrategia. Y este miembro debe inyectarse, 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
doStuff(...)
método.fuente
Lo que tiene es modular y eso es bueno, pero si tuviera que agrupar estas responsabilidades en IVideoProcessor, eso probablemente tendría más sentido desde el punto de vista DDD.
Por otro lado, si la división, la compresión y la indexación no estuvieran relacionadas de ninguna manera, las mantendría como componentes separados.
fuente
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, tiene una clase VideoCompressor. ¿Por qué está trabajando con una clase diseñada para comprimir video? ¿Por qué no tiene una clase de Video con métodos para comprimir los datos (de 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 representen actividades que pueda aplicar. En los viejos tiempos, las clases se llamaban tipos: OO era una forma de extender un idioma con soporte para nuevos tipos de datos. Si piensas en OO así, obtienes una mejor manera de diseñar tus clases.
EDITAR:
déjame intentar explicarme un poco mejor, imagina una clase de cadena que tiene un método concat. Puede implementar tal cosa donde cada objeto instanciado de la clase contiene los datos de la cadena, por lo que puede decir
pero el OP quiere que funcione así:
ahora hay lugares donde una clase se puede usar para mantener una colección de funciones relacionadas, pero eso no es OO, es una forma práctica de usar las características OO de un lenguaje para ayudar a administrar mejor su base de código, pero de ninguna manera de "OO Design". 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 clase, pero ya no necesita crear una instancia de un objeto solo para invocar los métodos. Terminas con métodos como los
string.IsNullOrEmpty(mystring)
que creo que son pobres en comparaciónmystring.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 opta por "una clase es un montón de métodos", entonces termina escribiendo un código de estilo "mejor C". (que no es necesariamente algo malo si está mejorando el código C) pero no le dará el mejor programa diseñado por OO.
fuente
Video
y tiende a exagerar tales clases con funcionalidad, que a menudo termina en un código desordenado con> 10K LOC por clase. El diseño avanzado de OO desglosa la funcionalidad en unidades más pequeñas como unaVideoCompressor
(y permite que unaVideo
clase solo sea una clase de datos o una fachada para laVideoCompressor
).VideoCompressor
no representa un objeto . No hay nada de malo en eso, solo muestra el límite del diseño orientado a objetos.Video
clase, pero la metodología de compresión no es en absoluto trivial, por lo que realmente dividiría elcompress
método en varios otros métodos privados. Esta es parte de la razón de mi pregunta, después de haber leído No crear clases de verbosVideo
clase, pero estaría compuesta de aVideoCompressor
, aVideoSplitter
y otras clases relacionadas, que deberían, en buena forma OO, ser clases individuales altamente cohesivas.El ISP (principio de segregación de interfaz) dice que ningún cliente debería verse obligado a depender de 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 esta es o no una mejor solución depende del escenario. El beneficio de esto es que, al usar 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
, estas clases parecen ser servicios (y, por lo tanto, sin estado). ¿Has pensado en hacerlos solteros?
fuente
Sí, hay un problema Pero no severo. 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 cambia la representación de su video (en su caso, está agrupado en la clase RawVideo), deberá actualizar todas sus clases de operación. O considere que puede tener múltiples representaciones de video que varían en tiempo de ejecución. Entonces tendrá que hacer coincidir la representación con una clase particular de "operación". También subjetivamente es molesto arrastrar una dependencia para cada operación que desee hacer en smth. y para actualizar la lista de dependencias que se pasan cada vez que decide que la operación ya no es necesaria o necesita una nueva operación.
También eso es en realidad una violación de SRP. Algunas personas simplemente tratan a SRP como una guía para dividir las responsabilidades (y lo llevan demasiado lejos al tratar cada operación como una responsabilidad distinta) pero se olvidan de que SRP también es una guía para agrupar responsabilidades. Y de acuerdo con las responsabilidades de SRP que cambian por la misma razón, deben agruparse para que, si se produce un cambio, se localice 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 algún conocimiento que no debería conocerse 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.
fuente