Un'immagine può essere ridimensionata in OOP?

9

Sto scrivendo un'app con un'entità Image e sto già avendo difficoltà a decidere a chi spetta ogni compito.

Per prima cosa ho la classe Image . Ha un percorso, una larghezza e altri attributi.

Poi ho creato una classe ImageRepository , per il recupero di immagini con un metodo singolo e testato, ad esempio: findAllImagesWithoutThumbnail() .

Ma ora devo anche essere in grado di createThumbnail() . Chi dovrebbe occuparsene? Stavo pensando di avere una classe ImageManager , che sarebbe una classe specifica per l'app (ci sarebbe anche un componente riutilizzabile di manipolazione delle immagini di terze parti, non sto reinventando la ruota).

O forse sarebbe 0K lasciare che il Image si ridimensiona da solo? Oppure lasciare che ImageRepository e ImageManager siano della stessa classe?

Che ne pensi?

    
posta ChocoDeveloper 31.08.2012 - 04:37
fonte

11 risposte

8

La domanda è troppo vaga per avere una risposta reale in quanto dipende da come verranno utilizzati gli oggetti Image .

Se si sta usando l'immagine in una sola dimensione e si sta ridimensionando perché l'immagine di origine ha le dimensioni sbagliate, potrebbe essere meglio fare il ridimensionamento del codice letto. Avere il tuo metodo createImage prendere una larghezza / altezza e quindi restituire l'immagine che è stata ridimensionata a quella larghezza / altezza.

Se hai bisogno di più dimensioni e se la memoria non è un problema, è meglio conservare l'immagine in memoria come originariamente letta e ridimensionare al momento della visualizzazione. In tal caso, ci sono un paio di disegni diversi che potresti usare. L'immagine width e height verrebbero corrette, ma avresti un metodo display che ha preso una posizione e un'altezza / larghezza di destinazione o avresti avuto un metodo che prendeva una larghezza / altezza che ha restituito un oggetto qualunque sia il sistema di visualizzazione che stai usando. La maggior parte delle API di disegno che ho utilizzato consentono di specificare la dimensione della destinazione al momento del disegno.

Se i requisiti fanno sì che il disegno di dimensioni diverse spesso abbastanza per le prestazioni sia un problema, potresti avere un metodo che crea una nuova immagine di una dimensione diversa in base all'originale. Un'alternativa sarebbe quella di avere la tua Image cache di classi di rappresentazioni diverse internamente in modo che la prima volta che chiami display con le dimensioni della miniatura esegua il ridimensionamento mentre la seconda volta semplicemente disegna la copia memorizzata nella cache salvata dall'ultima volta. Questo usa più memoria, ma è raro che tu abbia più di qualche risistemazione comune.

Un'altra alternativa è avere una singola classe Image che conserva l'immagine di base e che tale classe contenga una o più rappresentazioni. Un Image di per sé non avrebbe un'altezza / larghezza. Invece, inizierebbe con un ImageRepresentation che aveva un'altezza e una larghezza. Disegneresti questa rappresentazione. Per "ridimensionare" un'immagine, chiederebbe a Image una rappresentazione con determinate metriche di altezza / larghezza. Ciò causerebbe quindi contenere questa nuova rappresentazione così come l'originale. Questo ti dà un grande controllo su ciò che è in giro nella memoria a costo di una complessità extra.

Personalmente non mi piacciono le classi che contengono la parola Manager perché "manager" è una parola molto vaga che in realtà non ti dice molto su esattamente cosa fa la classe. Gestisce la durata dell'oggetto? Sta tra il resto dell'applicazione e la cosa che gestisce?

    
risposta data 31.08.2012 - 05:27
fonte
6

Mantieni le cose semplici finché ci sono solo alcuni requisiti e migliora il tuo design quando necessario. Immagino che nella maggior parte dei casi reali non ci sia nulla di male da iniziare con un progetto del genere:

class Image
{
    public Image createThumbnail(int sizeX, int sizeY)
    {
         // ...
         // later delegate the actual resize operation to a separate component
    }
}

Le cose possono diventare diverse quando devi passare più parametri a createThumbnail() , e quei parametri hanno bisogno di una vita propria. Ad esempio, supponiamo di voler creare miniature per parecchie centinaia di immagini, tutte con una certa dimensione di destinazione, algoritmo di ridimensionamento o qualità. Ciò significa che potresti spostare createThumbnail in un'altra classe, ad esempio una classe manager o una classe ImageResizer , in cui quei parametri vengono passati dal costruttore e quindi sono vincolati alla durata del ImageResizer oggetto.

In realtà, inizierei con il primo approccio e refactoring più avanti, quando ne avrò davvero bisogno.

    
risposta data 31.08.2012 - 08:40
fonte
4

Penso che dovrebbe essere parte della classe Image , in quanto il ridimensionamento in una classe esterna richiederebbe al resizer di conoscere l'implementazione di Image , violando così l'incapsulamento. Suppongo che Image sia una classe base e finirai con sottoclassi separate per i tipi di immagine concreta (PNG, JPEG, SVG, ecc.). Pertanto, dovrai avere classi di ridimensionamento corrispondenti o un resizer generico con un'istruzione switch che viene ridimensionata in base alla classe di implementazione, un odore di design classico.

Un approccio potrebbe essere quello di avere il costruttore Image di prendere una risorsa contenente l'immagine, i parametri di altezza e larghezza e crearsi in modo appropriato. Quindi il ridimensionamento potrebbe essere semplice come creare un nuovo oggetto utilizzando la risorsa originale (memorizzata nella cache dell'immagine) e i nuovi parametri di dimensione. Per esempio. foo.createThumbnail() sarebbe semplicemente return new Image(this.source, 250, 250) . (con Image è il tipo concreto di foo , ovviamente). Ciò mantiene le tue immagini immutabili e le loro implementazioni private.

    
risposta data 31.08.2012 - 16:29
fonte
4

So che OOP tratta dell'incapsulamento di dati e comportamenti insieme, ma non penso che sia una buona idea per un'immagine avere la logica di ridimensionamento incorporata in questo caso, perché un'immagine non ha bisogno sapere come ridimensionare se stesso per essere un'immagine.

Una miniatura è in realtà un'immagine diversa. Forse potresti avere una struttura dati che mantiene la relazione tra una fotografia e la sua miniatura (che sono entrambe immagini).

Cerco di dividere i miei programmi in cose (come immagini, fotografie, miniature, ecc.) e servizi (come il repository fotografico, ThumbnailGenerator, ecc.). Ottieni le strutture dati corrette e quindi definisci i servizi che ti consentono di creare, manipolare, trasformare, mantenere e ripristinare tali strutture dati. Non inserisco più comportamenti nelle mie strutture dati che nell'assicurarmi che vengano creati correttamente e utilizzati in modo appropriato.

Pertanto, no, un'immagine non dovrebbe contenere la logica su come creare una miniatura. Ci dovrebbe essere un servizio ThumbnailGenerator che abbia un metodo come:

Image GenerateThumbnailFrom(Image someImage);

La mia struttura dati più grande potrebbe essere simile a questa:

class Photograph : Image
{
    public Photograph(Image thumbnail)
    {
        if(thumbnail == null) throw new ArgumentNullException("thumbnail");
        this.Thumbnail = thumbnail;
    }

    public Image Thumbnail { get; private set; }
}

Ovviamente questo potrebbe significare che stai facendo uno sforzo che non vuoi fare mentre costruisci l'oggetto, quindi considererei qualcosa di simile anche a OK:

class Photograph : Image
{
    private Image thumbnail = null;
    private readonly Func<Image,Image> generateThumbnail;

    public Photograph(Func<Image,Image> generateThumbnail)
    {
        this.generateThumbnail = generateThumbnail;
    }


    public Image Thumbnail
    {
        get
        {
            if(this.thumbnail == null)
            {
                this.thumbnail = this.generateThumbnail(this);
            }
            return this.thumbnail;
        }
    }
}

... nel caso in cui si desideri una struttura dati con valutazione lazy. (Scusa se non ho incluso i miei assegni null e non l'ho reso thread-safe, che è qualcosa che vorresti se stessimo cercando di imitare una struttura dati immutabile).

Come puoi vedere, una di queste classi viene costruita da una specie di PhotographRepository, che probabilmente ha un riferimento a un ThumbnailGenerator che ha ottenuto tramite l'iniezione delle dipendenze.

    
risposta data 31.08.2012 - 21:09
fonte
3

Hai identificato una singola funzionalità che desideri implementare, quindi perché non dovrebbe essere separata da tutto ciò che hai identificato finora? Questo è ciò che il principio di singola responsabilità suggerisce è la soluzione.

Crea un'interfaccia IImageResizer che ti permetta di passare un'immagine e una dimensione target e che restituisca una nuova immagine. Quindi creare un'implementazione di tale interfaccia. Ci sono in realtà un sacco di modi per ridimensionare le immagini, quindi potresti persino trovarne più di una!

    
risposta data 31.08.2012 - 07:10
fonte
3

Presumo quei fatti sul metodo, che ridimensiona le immagini:

  • Deve restituire una nuova copia dell'immagine. Non è possibile modificare l'immagine stessa, perché si romperà l'altro codice che ha riferimento a questa immagine.
  • Non ha bisogno di accedere ai dati interni della classe Image. Le classi di immagini di solito devono offrire l'accesso pubblico a quei dati (o copie di).
  • Il ridimensionamento delle immagini è complesso e richiede molti parametri diversi. Forse anche punti di estensibilità per diversi algoritmi di ridimensionamento. Passare tutto risulterebbe in una grande firma del metodo.

Sulla base di questi fatti, direi che non c'è alcun motivo per cui il metodo di ridimensionamento delle immagini faccia parte della classe Image stessa. Implementarlo come metodo di classe helper statico sarebbe il migliore.

    
risposta data 31.08.2012 - 08:32
fonte
2

Una classe di elaborazione delle immagini potrebbe essere appropriata (o Image Manager, come hai chiamato tu). Passa l'immagine a un metodo CreateThumbnail del processore di immagini, ad esempio, per recuperare un'immagine in miniatura.

Una delle ragioni per cui suggerirei questa rotta è che tu stia utilizzando una libreria di elaborazione di immagini di terze parti. Prendere la funzionalità di ridimensionamento dalla classe Image potrebbe rendere più semplice l'isolamento di qualsiasi codice specifico di piattaforma o di terze parti. Quindi, se è possibile utilizzare la classe Image di base su tutte le piattaforme / app, non è necessario inquinarla con la piattaforma o il codice specifico della libreria. Tutto ciò si può trovare nell'Image Processor.

    
risposta data 31.08.2012 - 09:11
fonte
2

Fondamentalmente, come già detto Doc Brown:

Crea un metodo getAsThumbnail() per la classe immagine, ma in realtà questo metodo dovrebbe delegare il lavoro solo a qualche classe ImageUtils . Quindi sarebbe simile a questo:

 class Image{
   // ...
   public Thumbnail getAsThumbnail{
     return ImageUtils.convertToThumbnail(this);
   }
   // ...
 }

E

 class ImageUtils{
   // ...
   public static Thumbnail convertToThumbnail(Image i){
     // ...
   }
   // ...
 }

Ciò consentirà un codice più semplice da osservare. Confronta il seguente:

Image i = ...
someComponent.setThumbnail(i.getAsThumbnail());

o

Image i = ...
Thumbnail t = ImageUtils.convertToThumbnail(i);
someComponent.setThumbnail(t); 

Se quest'ultimo ti sembra utile, puoi anche limitarti a creare questo metodo di supporto da qualche parte.

    
risposta data 31.08.2012 - 17:58
fonte
1

Penso nel "Dominio delle immagini", hai solo l'oggetto Immagine che è immutabile e monadico. Chiedi l'immagine per una versione ridimensionata e restituisce una versione ridimensionata di se stessa. Quindi puoi decidere se vuoi eliminare l'originale o mantenerlo entrambi

Ora le versioni thumbnail, avatar, etc dell'immagine sono completamente un altro dominio, che può chiedere al dominio Image le diverse versioni di una determinata immagine da assegnare a un utente. Di solito questo dominio non è nemmeno così grande o generico, quindi puoi probabilmente tenerlo nella logica dell'applicazione.

In un'applicazione su piccola scala, ridimensionerei le immagini al momento della lettura. Ad esempio, potrei avere una regola di riscrittura di apache che deleghi a php uno script se l'immagine 'http://my.site.com/images/thumbnails/image1.png', dove il file verrà recuperato usando il nome image1.png e ridimensionato e memorizzato in 'thumbnails / image1.png'. Quindi alla prossima richiesta a questa stessa immagine, apache servirà l'immagine direttamente senza eseguire lo script php. La tua domanda di findAllImagesWithoutThumbnails risponde automaticamente ad apache, a meno che non sia necessario fare statistiche?

In un'app di grandi dimensioni, invierei tutte le nuove immagini a un lavoro in background, che si occupa di generare le diverse versioni dell'immagine e di salvarla in luoghi appropriati. Non mi preoccuperei di creare un intero dominio o classe in quanto è improbabile che questo dominio si trasformi in un terribile caos di spaghetti e cattiva salsa.

    
risposta data 25.10.2012 - 13:08
fonte
0

Risposta breve:

La mia raccomandazione è l'aggiunta di questi metodi alla classe immagine:

public Image getResizedVersion(int width, int height);
public Image getResizedVersion(double percentage);

L'oggetto Immagine è ancora inmutabile, questi metodi restituiscono una nuova immagine.

    
risposta data 03.10.2012 - 17:01
fonte
0

Ci sono già alcune grandi risposte, quindi approfondirò un po 'su un'euristica dietro al modo di identificare gli oggetti e le loro responsabilità.

OOP differisce dalla vita reale in quanto gli oggetti nella vita reale sono spesso passivi e in OOP sono attivi. E questo è un elemento centrale del pensiero degli oggetti . Ad esempio, chi ridimensionerebbe un'immagine nella vita reale? Un umano, che è intelligente in questo senso. Ma in OOP non ci sono umani, quindi gli oggetti sono invece intelligenti. Un modo per implementare questo approccio "human-centric" in OOP è l'utilizzo di classi di servizio, ad esempio, famigerate classi Manager . Quindi gli oggetti sono trattati come dati passivi-struture. Non è un modo OOP.

Quindi ci sono due opzioni. Il primo, creando un metodo Image::createThumbnail() , è già considerato. Il secondo è quello di creare una classe ResizedImage . Può essere un decoratore di Image (dipende dal tuo dominio se preservare un'interfaccia Image o meno), sebbene risulti in alcuni problemi di incapsulamento, poiché ResizedImage dovrebbe avere un Image fonte. Ma un Image non sarebbe sopraffatto dal ridimensionamento dei dettagli, lasciandolo a un oggetto dominio separato, che agisce in accordo con un SRP.

    
risposta data 13.12.2017 - 09:29
fonte

Leggi altre domande sui tag