Estou escrevendo um aplicativo que terá uma Image
entidade e já estou tendo problemas para decidir de quem é a responsabilidade de cada tarefa.
Primeiro eu tenho a Image
aula. Possui um caminho, largura e outros atributos.
Em seguida, criaram uma ImageRepository
classe, para obter imagens com um método simples e testados, por exemplo: findAllImagesWithoutThumbnail()
.
Mas agora eu também preciso ser capaz createThumbnail()
. Quem deve lidar com isso? Eu estava pensando em ter uma ImageManager
classe, que seria uma classe específica do aplicativo (também haveria um componente reutilizável de manipulação de imagem de terceiros, não estou reinventando a roda).
Ou talvez seja 0K para deixar o Image
redimensionamento em si? Ou deixar a ImageRepository
e ImageManager
ser da mesma classe?
O que você acha?
fonte
Respostas:
A pergunta feita é muito vaga para ter uma resposta real, pois realmente depende de como os
Image
objetos serão usados.Se você estiver usando apenas a imagem em um tamanho e estiver redimensionando porque a imagem de origem é do tamanho errado, talvez seja melhor que o código de leitura faça o redimensionamento. Faça com que seu
createImage
método tenha uma largura / altura e retorne a imagem redimensionada nessa largura / altura.Se você precisar de vários tamanhos e se a memória não for uma preocupação, é melhor manter a imagem na memória como lida originalmente e redimensionar no momento da exibição. Nesse caso, existem alguns modelos diferentes que você pode usar. A imagem
width
eheight
seria corrigida, mas você teria umdisplay
método que assumisse uma posição e uma altura / largura de destino ou teria algum método usando uma largura / altura que retornasse um objeto a qualquer sistema de exibição que você estiver usando. A maioria das APIs de desenho que usei permitem especificar o tamanho do destino no momento da criação.Se os requisitos causarem desenhos em tamanhos diferentes com frequência suficiente para que o desempenho seja uma preocupação, você pode ter um método que crie uma nova imagem de tamanho diferente com base no original. Uma alternativa seria fazer com que sua
Image
classe armazene em cache diferentes representações internamente, para que, na primeira vez em que você chamedisplay
com o tamanho da miniatura, ela seja redimensionada, na segunda vez que apenas desenhe a cópia em cache salva da última vez. Isso usa mais memória, mas é raro você ter mais do que alguns redimensionamentos comuns.Outra alternativa é ter uma única
Image
classe que retém a imagem base e que essa classe contenha uma ou mais representações. Um emImage
si não teria altura / largura. Em vez disso, começaria com umImageRepresentation
que tivesse altura e largura. Você desenharia essa representação. Para "redimensionar" uma imagem, soliciteImage
uma representação com determinadas métricas de altura / largura. Isso faria com que ela contivesse essa nova representação, bem como a original. Isso oferece muito controle sobre exatamente o que está pendurado na memória, com o custo de complexidade extra.Pessoalmente, não gosto de classes que contenham a palavra
Manager
porque "gerente" é uma palavra muito vaga que realmente não diz muito sobre exatamente o que a classe faz. Ele gerencia a vida útil do objeto? Ele fica entre o restante do aplicativo e o que ele gerencia?fonte
Mantenha as coisas simples, desde que haja apenas alguns requisitos, e melhore seu design quando necessário. Acho que na maioria dos casos do mundo real não há nada de errado em começar com um design como esse:
As coisas podem se tornar diferentes quando você precisa passar mais parâmetros
createThumbnail()
e esses parâmetros precisam ter uma vida útil própria. Por exemplo, vamos supor que você criará miniaturas para várias centenas de imagens, todas com algum tamanho de destino, algoritmo de redimensionamento ou qualidade. Isso significa que você pode movercreateThumbnail
para outra classe, por exemplo, uma classe de gerente ou umaImageResizer
classe, onde esses parâmetros são passados pelo construtor e, portanto, estão vinculados à vida útil doImageResizer
objeto.Na verdade, começaria com a primeira abordagem e refatoraria mais tarde, quando realmente preciso.
fonte
ImageResizer
classe em primeiro lugar? Quando você delega uma ligação em vez de passar a responsabilidade para uma nova classe?Image thumbnail = img.createThumbnail(x,y)
.Eu acho que teria que fazer parte da
Image
classe, pois o redimensionamento em uma classe externa exigiria que o redimensionador conhecesse a implementação doImage
, violando o encapsulamento. Suponho queImage
seja uma classe base, e você acabará com subclasses separadas para tipos de imagem concretos (PNG, JPEG, SVG etc.). Portanto, você precisará ter classes de redimensionamento correspondentes ou um redimensionador genérico com umaswitch
declaração que seja redimensionada com base na classe de implementação - um cheiro de design clássico.Uma abordagem pode ser fazer com que o
Image
construtor pegue um recurso contendo os parâmetros de imagem e altura e largura e crie-se adequadamente. O redimensionamento pode ser tão simples quanto criar um novo objeto usando o recurso original (armazenado em cache na imagem) e os novos parâmetros de tamanho. Por exemplo,foo.createThumbnail()
seria simplesmentereturn new Image(this.source, 250, 250)
. (Image
sendo o tipo concretofoo
, é claro). Isso mantém suas imagens imutáveis e suas implementações privadas.fonte
Image
. Tudo o que precisa é a origem e as dimensões de destino.Sei que o POO envolve encapsular dados e comportamento juntos, mas não acho que seja uma boa ideia que uma imagem tenha a lógica de redimensionamento incorporada nesse caso, porque uma imagem não precisa saber como se redimensionar para ser uma imagem.
Uma miniatura é na verdade uma imagem diferente. Talvez você tenha uma estrutura de dados que mantenha o relacionamento entre uma Fotografia e sua Miniatura (ambas são Imagens).
Tento dividir meus programas em coisas (como Imagens, Fotografias, Miniaturas, etc.) e Serviços (como PhotographRepository, ThumbnailGenerator, etc.). Acerte suas estruturas de dados e defina os serviços que permitem criar, manipular, transformar, persistir e recuperar essas estruturas de dados. Eu não coloco mais comportamento em minhas estruturas de dados do que garantir que elas sejam criadas corretamente e usadas adequadamente.
Portanto, não, uma imagem não deve conter a lógica de como criar uma miniatura. Deve haver um serviço ThumbnailGenerator que tenha um método como:
Minha estrutura de dados maior pode ficar assim:
Claro que isso pode significar que você está fazendo um esforço que não deseja ao construir o objeto, então eu consideraria algo assim também:
... no caso em que você deseja uma estrutura de dados com avaliação lenta. (Desculpe, não incluí minhas verificações nulas e não a tornei segura para threads, o que é algo que você deseja se estiver tentando imitar uma estrutura de dados imutável).
Como você pode ver, qualquer uma dessas classes está sendo criada por algum tipo de PhotographRepository, que provavelmente tem uma referência a um ThumbnailGenerator que foi obtido por injeção de dependência.
fonte
Você identificou uma única funcionalidade que deseja implementar. Por que não deveria estar separada de tudo o que identificou até agora? É isso que o Princípio da Responsabilidade Única sugere que é a solução.
Crie uma
IImageResizer
interface que permita passar uma imagem e um tamanho de destino e que retorne uma nova imagem. Em seguida, crie uma implementação dessa interface. Na verdade, existem várias maneiras de redimensionar imagens, para que você possa acabar com mais de uma!fonte
Eu assumo esses fatos sobre o método, que redimensiona imagens:
Com base nesses fatos, eu diria que não há razão para o método de redimensionamento de imagem fazer parte da própria classe de imagem. Implementá-lo como método de classe de auxiliar estático seria o melhor.
fonte
Uma classe de Processamento de imagem pode ser apropriada (ou Image Manager, como você a chamou). Passe sua imagem para um método CreateThumbnail do processador de imagens, por exemplo, para recuperar uma imagem em miniatura.
Uma das razões pelas quais eu sugeriria essa rota é que você diz estar usando uma biblioteca de processamento de imagens de terceiros. Remover a funcionalidade de redimensionamento da própria classe Image pode facilitar o isolamento de qualquer código específico de plataforma ou de terceiros. Portanto, se você puder usar sua classe Image básica em todas as plataformas / aplicativos, não precisará poluí-la com código específico de plataforma ou biblioteca. Tudo isso pode estar localizado no processador de imagens.
fonte
Basicamente, como Doc Brown já disse:
Crie um
getAsThumbnail()
método para a classe de imagem, mas esse método deve apenas delegar o trabalho a algumaImageUtils
classe. Portanto, seria algo como isto:E
Isso permitirá um código mais fácil de olhar. Compare o seguinte:
Ou
Se o último parecer bom para você, você também pode continuar criando esse método auxiliar em algum lugar.
fonte
Eu acho que no "Domínio da Imagem", você apenas tem o objeto Imagem que é imutável e monádico. Você solicita à imagem uma versão redimensionada e ela retorna uma versão redimensionada de si mesma. Depois, você pode decidir se deseja se livrar do original ou manter os dois.
Agora, as versões em miniatura, avatar etc. da imagem são outro domínio inteiramente, que pode solicitar ao domínio da imagem versões diferentes de uma determinada imagem para fornecer a um usuário. Normalmente, esse domínio também não é tão grande ou genérico; portanto, você provavelmente pode manter isso na lógica do aplicativo.
Em um aplicativo de pequena escala, eu redimensionava as imagens em tempo de leitura. Por exemplo, eu poderia ter uma regra de reescrita do apache que delega para php um script se a imagem 'http://my.site.com/images/thumbnails/image1.png', onde o arquivo será recuperado usando o nome image1.png e redimensionado e armazenado em 'thumbnails / image1.png'. Então, na próxima solicitação para essa mesma imagem, o apache exibirá a imagem diretamente, sem executar o script php. Sua pergunta sobre findAllImagesWithoutThumbnails é respondida automaticamente pelo apache, a menos que você precise fazer estatísticas?
Em um aplicativo de grande escala, eu enviava todas as novas imagens para um trabalho em segundo plano, que cuida de gerar as diferentes versões da imagem e as salva nos locais apropriados. Eu não me incomodaria em criar um domínio ou uma classe inteira, pois é improvável que esse domínio se transforme em uma terrível bagunça de espaguete e molho ruim.
fonte
Resposta curta:
Minha recomendação é adicionar esses métodos à classe de imagem:
O objeto Image ainda é imutável, esses métodos retornam uma nova imagem.
fonte
Já existem algumas ótimas respostas, então irei elaborar um pouco sobre uma heurística por trás do caminho para identificar objetos e suas responsabilidades.
OOP difere da vida real, pois os objetos na vida real geralmente são passivos e, na OOP, eles são ativos. E esse é o núcleo do pensamento sobre objetos . Por exemplo, quem redimensionaria uma imagem na vida real? Um humano, que é inteligente a esse respeito. Mas no POO não há humanos; portanto, os objetos são inteligentes. Uma maneira de implementar essa abordagem "centrada no ser humano" no OOP é utilizar classes de serviço, por exemplo,
Manager
classes notórias . Assim, os objetos são tratados como dados passivos. Não é uma maneira de POO.Portanto, existem duas opções. O primeiro, criando um método
Image::createThumbnail()
, já é considerado. O segundo é criar umaResizedImage
classe. Pode ser um decorador de umImage
(depende do seu domínio preservarImage
ou não uma interface), embora isso resulte em alguns problemas de encapsulamento, poisResizedImage
precisaria ter aImage
fonte de uma. Mas umImage
não seria sobrecarregado com detalhes de redimensionamento, deixando-o em um objeto de domínio separado, agindo de acordo com um SRP.fonte