Estoy escribiendo una aplicación que tendrá una Image
entidad, y ya estoy teniendo problemas para decidir de quién es la responsabilidad de cada tarea.
Primero tengo la Image
clase. Tiene una ruta, ancho y otros atributos.
Entonces creé una ImageRepository
clase, para la recuperación de imágenes con un sencillo y probado método, por ejemplo: findAllImagesWithoutThumbnail()
.
Pero ahora también necesito poder hacerlo createThumbnail()
. ¿Quién debería ocuparse de eso? Estaba pensando en tener una ImageManager
clase, que sería una clase específica de la aplicación (también habría un componente reutilizable de manipulación de imágenes de terceros, no estoy reinventando la rueda).
¿O tal vez sería 0K dejar que el cambio de Image
tamaño en sí mismo? ¿O dejar que ImageRepository
y ImageManager
sea la misma clase?
¿Qué piensas?
fuente
Respuestas:
La pregunta que se hace es demasiado vaga para tener una respuesta real, ya que realmente depende de cómo
Image
se van a utilizar los objetos.Si solo está usando la imagen en un tamaño y está cambiando el tamaño porque la imagen de origen es del tamaño incorrecto, puede ser mejor que el código de lectura cambie el tamaño. Haga que su
createImage
método tome un ancho / alto y luego devuelva la imagen que ha cambiado de tamaño a ese ancho / alto.Si necesita varios tamaños, y si la memoria no es una preocupación, es mejor mantener la imagen en la memoria como se leyó originalmente y cambiar el tamaño en el momento de la visualización. En tal caso, hay un par de diseños diferentes que puede usar. La imagen
width
yheight
sería fija, pero tendría undisplay
método que tomara una posición y un alto / ancho objetivo o tendría algún método que tomara un ancho / alto que devolviera un objeto que sea cual sea el sistema de visualización que esté utilizando. La mayoría de las API de dibujo que he usado le permiten especificar el tamaño objetivo en el momento del dibujo.Si los requisitos provocan que el dibujo sea de diferentes tamaños con la frecuencia suficiente como para que el rendimiento sea una preocupación, podría tener un método que cree una nueva imagen de un tamaño diferente en función del original. Una alternativa sería hacer que su
Image
clase almacene diferentes representaciones internamente para que la primera vez que llamedisplay
con el tamaño de miniatura aumente el tamaño, mientras que la segunda vez simplemente dibuje la copia en caché que guardó la última vez. Esto usa más memoria, pero es raro que tenga más de unos pocos cambios de tamaño comunes.Otra alternativa es tener una sola
Image
clase que conserve la imagen base y que esa clase contenga una o más representaciones. UnImage
sí mismo no tendría una altura / ancho. En cambio, comenzaría con unoImageRepresentation
que tuviera altura y anchura. Dibujarías esta representación. Para "redimensionar" una imagen, le pediríaImage
una representación con ciertas métricas de alto / ancho. Esto provocaría que contuviera esta nueva representación, así como la original. Esto le da un gran control sobre exactamente lo que está en la memoria a costa de una complejidad adicional.Personalmente, no me gustan las clases que contienen la palabra
Manager
porque "gerente" es una palabra muy vaga que realmente no te dice mucho sobre exactamente lo que hace la clase. ¿Gestiona la vida útil del objeto? ¿Se interpone entre el resto de la aplicación y lo que administra?fuente
Mantenga las cosas simples siempre que solo haya unos pocos requisitos y mejore su diseño cuando sea necesario. Supongo que en la mayoría de los casos del mundo real no hay nada de malo en comenzar con un diseño como ese:
Las cosas pueden volverse diferentes cuando necesita pasar más parámetros
createThumbnail()
, y esos parámetros necesitan una vida propia. Por ejemplo, supongamos que va a crear miniaturas para varios cientos de imágenes, todas con un determinado tamaño de destino, algoritmo o calidad de cambio de tamaño. Eso significará que podría movercreateThumbnail
a otra clase, por ejemplo, una clase de administrador o unaImageResizer
clase, donde el constructor pasa esos parámetros y, por lo tanto, están vinculados a la vida útil delImageResizer
objeto.En realidad, comenzaría con el primer enfoque y refactorizaría más tarde cuando realmente lo necesite.
fuente
ImageResizer
clase en primer lugar? ¿Cuándo delega una llamada en lugar de trasladar la responsabilidad a una nueva clase?Image thumbnail = img.createThumbnail(x,y)
.Creo que debería ser parte de la
Image
clase, ya que cambiar el tamaño en una clase externa requeriría que el redimensionador conozca la implementación de la claseImage
, lo que viola la encapsulación. Supongo queImage
es una clase base, y terminarás con subclases separadas para tipos de imágenes concretas (PNG, JPEG, SVG, etc.). Por lo tanto, deberá tener las clases de cambio de tamaño correspondientes o un cambio de tamaño genérico con unaswitch
declaración que cambie de tamaño según la clase de implementación: un olor de diseño clásico.Un enfoque podría ser que su
Image
constructor tome un recurso que contenga la imagen y los parámetros de altura y anchura y se cree adecuadamente. Luego, cambiar el tamaño podría ser tan simple como crear un nuevo objeto utilizando el recurso original (almacenado en caché dentro de la imagen) y los nuevos parámetros de tamaño. Por ejemplofoo.createThumbnail()
, simplementereturn new Image(this.source, 250, 250)
. (conImage
ser el tipo concreto defoo
, por supuesto). Esto mantiene sus imágenes inmutables y sus implementaciones privadas.fuente
Image
. Todo lo que necesita es la fuente y las dimensiones de destino.Sé que OOP se trata de encapsular datos y comportamiento juntos, pero no creo que sea una buena idea que una Imagen tenga la lógica de cambio de tamaño incrustada en este caso, porque una Imagen no necesita saber cómo cambiar el tamaño para ser una imagen.
Una miniatura es en realidad una imagen diferente. Quizás tenga una estructura de datos que mantenga la relación entre una Fotografía y su Miniatura (ambas son Imágenes).
Intento dividir mis programas en cosas (como Imágenes, Fotografías, Miniaturas, etc.) y Servicios (como PhotographRepository, ThumbnailGenerator, etc.). Obtenga las estructuras de datos correctas y luego defina los servicios que le permiten crear, manipular, transformar, persistir y recuperar esas estructuras de datos. No pongo más comportamiento en mis estructuras de datos que asegurarme de que se creen correctamente y se usen adecuadamente.
Por lo tanto, no, una imagen no debe contener la lógica sobre cómo hacer una miniatura. Debe haber un servicio ThumbnailGenerator que tenga un método como:
Mi estructura de datos más grande podría verse así:
Por supuesto, eso podría significar que está haciendo un esfuerzo que no desea hacer mientras construye el objeto, por lo que consideraría algo como esto también:
... en el caso de que desee una estructura de datos con evaluación diferida. (Lo siento, no incluí mis comprobaciones nulas y no lo hice seguro para subprocesos, que es algo que desearía si intentara imitar una estructura de datos inmutable).
Como puede ver, cualquiera de estas clases está siendo construida por algún tipo de PhotographRepository, que probablemente tiene una referencia a un ThumbnailGenerator que obtuvo a través de la inyección de dependencia.
fuente
Ha identificado una única funcionalidad que desea implementar, entonces, ¿por qué no debería estar separada de todo lo que ha identificado hasta ahora? Eso es lo que sugeriría el Principio de responsabilidad única es la solución.
Cree una
IImageResizer
interfaz que le permita pasar una imagen y un tamaño de destino, y que devuelva una nueva imagen. Luego cree una implementación de esa interfaz. En realidad, hay muchas maneras de cambiar el tamaño de las imágenes, por lo que incluso podría terminar con más de una.fuente
Asumo esos hechos sobre el método, que cambia el tamaño de las imágenes:
Basado en esos hechos, diría que no hay razón para que el método de cambio de tamaño de la imagen sea parte de la clase Image en sí. Implementarlo como método auxiliar de clase estático sería lo mejor.
fuente
Una clase de Procesamiento de imágenes puede ser apropiada (o Administrador de imágenes, como lo llamó). Pase su imagen a un método CreateThumbnail del procesador de imágenes, por ejemplo, para recuperar una imagen en miniatura.
Una de las razones por las que sugeriría esta ruta es que usted dice que está utilizando una biblioteca de procesamiento de imágenes de terceros. Eliminar la funcionalidad de cambio de tamaño de la clase Image en sí misma podría facilitarle el aislamiento de cualquier código de plataforma específico o de terceros. Entonces, si puede usar su clase de imagen base en todas las plataformas / aplicaciones, entonces no tiene que contaminarla con el código específico de la plataforma o biblioteca. Todo eso puede ubicarse en el procesador de imágenes.
fuente
Básicamente como Doc Brown ya dijo:
Cree un
getAsThumbnail()
método para la clase de imagen, pero este método debería delegar el trabajo en algunaImageUtils
clase. Entonces se vería algo así:Y
Esto permitirá un código más fácil de ver. Compare lo siguiente:
O
Si este último le parece bueno, también puede limitarse a crear este método auxiliar en alguna parte.
fuente
Creo que en el "Dominio de imagen", solo tienes el objeto Imagen que es inmutable y monádico. Pide a la imagen una versión redimensionada y devuelve una versión redimensionada de sí misma. Luego puede decidir si desea deshacerse del original o conservar ambos.
Ahora, las versiones en miniatura, avatar, etc. de la Imagen son completamente otro Dominio, que puede solicitar al dominio de la Imagen diferentes versiones de una determinada imagen para darle a un usuario. Por lo general, este dominio tampoco es tan grande o genérico, por lo que probablemente pueda mantener esto en la lógica de la aplicación.
En una aplicación a pequeña escala, cambiaría el tamaño de las imágenes en el momento de la lectura. Por ejemplo, podría tener una regla de reescritura de apache que delegue a php un script si la imagen 'http://my.site.com/images/thumbnails/image1.png', donde se recuperará el archivo usando el nombre image1.png y redimensionado y almacenado en 'thumbnails / image1.png'. Luego, en la siguiente solicitud a esta misma imagen, apache servirá la imagen directamente sin ejecutar el script php. Apache responde automáticamente a su pregunta de findAllImagesWithoutThumbnails, a menos que necesite hacer estadísticas.
En una aplicación a gran escala, enviaría todas las imágenes nuevas a un trabajo en segundo plano, que se encarga de generar las diferentes versiones de la imagen y las guarda en los lugares apropiados. No me molestaría en crear un dominio completo o una clase, ya que es muy poco probable que este dominio se convierta en un terrible desastre de espagueti y salsa picante.
fuente
Respuesta corta:
Mi recomendación es agregar estos métodos a la clase de imagen:
El objeto Imagen aún es inmutable, estos métodos devuelven una nueva imagen.
fuente
Ya hay bastantes respuestas excelentes, por lo que explicaré un poco sobre una heurística detrás de la forma de identificar objetos y sus responsabilidades.
OOP difiere de la vida real en que los objetos en la vida real son a menudo pasivos, y en OOP están activos. Y este es un núcleo del pensamiento de objetos . Por ejemplo, ¿quién cambiaría el tamaño de una imagen en la vida real? Un humano, que es inteligente en ese sentido. Pero en OOP no hay humanos, por lo que los objetos son inteligentes. Una forma de implementar este enfoque "centrado en el ser humano" en OOP es utilizar clases de servicio, por ejemplo,
Manager
clases notorias . Por lo tanto, los objetos se tratan como estructuras de datos pasivas. No es una forma de OOP.Entonces hay dos opciones. El primero, crear un método
Image::createThumbnail()
, ya está considerado. El segundo es crear unaResizedImage
clase. Puede ser un decorador de unImage
(depende de su dominio si preservar unaImage
interfaz o no), aunque da lugar a algunos problemas de encapsulación, yaResizedImage
que tendría que tener unaImage
fuente. Pero anImage
no se abrumaría con el cambio de tamaño de los detalles, dejándolo en un objeto de dominio separado, actuando de acuerdo con un SRP.fuente