Should an image be able to resize itself in OOP?

by ChocoDeveloper   Last Updated December 12, 2017 22:05 PM

I'm writing an app that will have an Image entity, and I'm already having trouble deciding whose responsibility each task should be.

First I have the Image class. It has a path, width, and other attributes.

Then I created an ImageRepository class, for retrieving images with a single and tested method, eg: findAllImagesWithoutThumbnail().

But now I also need to be able to createThumbnail(). Who should deal with that? I was thinking about having an ImageManager class, which would be an app-specific class (there would also be a third party image manipulation reusable component of choice, I'm not reinventing the wheel).

Or maybe it would be 0K to let the Image resize itself? Or let the ImageRepository and ImageManager be the same class?

What do you think?



Answers 10


The question as asked is too vague to have a real answer as it really depends on how the Image objects are going to be used.

If you are only using the image at one size, and are resizing because the source image is the wrong size, it might be best to have the read code do the resizing. Have your createImage method take a width/height and then return the image having been resized at that width/height.

If you need multiple sizes, and if memory isn't a concern, it is best to keep the image in memory as originally read and do any resizing at display time. In such a case, there are a couple different designs you might use. The image width and height would be fixed, but you'd either have a display method that took a position and a target height/width or you'd have some method taking a width/height that returned an object that whatever display system you are using. Most drawing APIs I've used let you specify target size at draw time.

If the requirements cause for drawing at a different sizes often enough for performance to be a concern, you could have a method that creates a new image of a different size based on the original. An alternative would be to have your Image class cache different representations internally so that the first time you call display with the thumbnail size it does the resizing while the second time it just draws the cached copy it saved from last time. This uses more memory, but it is rare you'll have more than a few common resizings.

Another alternative is to have a single Image class that retains the base image and have that class contain one or more representations. An Image itself wouldn't have a height/width. Instead, it would start with one ImageRepresentation that had a height and width. You'd draw this representation. To "resize" an image, you'd ask the Image for a representation with certain height/width metrics. This would cause it to then contain this new representation as well as the original. This gives you a lot of control over exactly what is hanging around in memory at the cost of extra complexity.

I personally dislike classes that contain the word Manager because "manager" is a very vague word that doesn't really tell you much about exactly what the class does. Does it manage object lifetime? Does it stand between the rest of the application and the thing it manages?

Steven Burnap
Steven Burnap
August 31, 2012 03:27 AM

You have identified a single functionality you want to implement, so why shouldn't it be separate from everything you have identified so far? That is what the Single Responsibility Principle would suggest is the solution.

Create an IImageResizer interface that allows you to pass in an image and a target size, and which returns a new image. Then create an implementation of that interface. There are actually tons of ways to resize images, so you could even end up with more than one!

Chris Pitman
Chris Pitman
August 31, 2012 05:10 AM

I assume those facts about method, that resizes images:

  • It must return new copy of image. You cannot modify the image itself, because it would break other code that has reference to this image.
  • It doesn't need access to the internal data of Image class. Image classes usually need to offer public access to those data (or copy of).
  • Image resizing is complex and requires many different parameters. Maybe even extensibility points for different resizing algorithms. Passing everything would result in big method signature.

Based on those facts, I would say that there is no reason for Image resizing method be part of Image class itself. Implementing it as static helper method of class would be best.

Euphoric
Euphoric
August 31, 2012 06:32 AM

Keep things simple as long as there are only a few requirements, and improve your design when you have to. I guess in most real-world cases there is nothing wrong to start with a design like that:

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

Things may become different when you need to pass more parameters to createThumbnail(), and those parameters need a lifetime of their own. For example, lets assume you are going to create thumbnails for several hundred images, all with some given destination size, resize algorithm or quality. That will mean that you could move the createThumbnail to another class, for example, a manager class, or an ImageResizer class, where those parameters are passed in by the constructor and so are bound to the lifetime of the ImageResizer object.

Actually, I would start with the first approach and refactor later when I really need it.

Doc Brown
Doc Brown
August 31, 2012 06:40 AM

An Image Processing class might be appropriate (or Image Manager, as you called it). Pass your Image to a CreateThumbnail method of the Image Processor, for example, to retrieve a thumbnail image.

One of the reasons I'd suggest this route is that you say you're using a 3rd party image processing library. Taking the resizing functionality out of the Image class itself might make it easier for you to isolate any platform specific or 3rd party code. So if you can use your base Image class across all platforms/apps, you then dont have to pollute it with platform or library specific code. That can all be located in the Image Processor.

GrandmasterB
GrandmasterB
August 31, 2012 07:11 AM

I think it would have to be part of the Image class, as resizing in an external class would require the resizer to know the implementation of the Image, thereby violating encapsulation. I'm assuming Image is a base class, and you'll wind up with separate subclasses for concrete image types (PNG, JPEG, SVG, etc). Therefore, you'll either have to have corresponding resizing classes, or a generic resizer with a switch statement that resizes based on the implementation class -- a classic design smell.

One approach might be to have your Image constructor take a resource containing the image, and height and width parameters and create itself appropriately. Then resizing could be as simple as creating a new object using the original resource (cached inside the image) and the new size parameters. E.g. foo.createThumbnail() would simply return new Image(this.source, 250, 250). (with Image being the concrete type of foo, of course). This keeps your images immutable and their implementations private.

TMN
TMN
August 31, 2012 14:29 PM

Basically as Doc Brown already said:

Create a getAsThumbnail() method for the image class, but this method should actually just delegate the work to some ImageUtils class. So it would look something like this:

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

And

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

This will allow easier-to-look-at code. Compare the following:

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

Or

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

If the latter one seems good for you, you can also stick to just creating this helper method somewhere.

Deiwin
Deiwin
August 31, 2012 15:58 PM

I know that OOP is about encapsulating data and behavior together, but I don't think it's a good idea for an Image to have the resize logic embedded in this case, because an Image doesn't need to know how to resize itself to be an Image.

A thumbnail is actually a different Image. Perhaps you might have a datastructure that holds the relationship between a Photograph and it's Thumbnail (both of which are Images).

I try to divide my programs into things (like Images, Photographs, Thumbnails, etc.) and Services (like PhotographRepository, ThumbnailGenerator, etc.). Get your data structures right, and then define the services that let you create, manipulate, transform, persist and recover those data structures. I don't put any more behavior in my data structures than making sure they're created properly and used appropriately.

Therefore, no, an Image shouldn't contain the logic about how to make a Thumbnail. There should be a ThumbnailGenerator service that has a method like:

Image GenerateThumbnailFrom(Image someImage);

My bigger data structure might look like this:

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

    public Image Thumbnail { get; private set; }
}

Of course that might mean you're doing effort you don't want to do while constructing the object, so I would consider something like this OK too:

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;
        }
    }
}

... in the case where you want a data structure with lazy evaluation. (Sorry I didn't include my null checks and I didn't make it thread-safe, which is something you'd want if you were trying to mimic an immutable data structure).

As you can see, either of these classes is being built by some kind of PhotographRepository, which probably has a reference to a ThumbnailGenerator that it got through dependency injection.

Scott Whitlock
Scott Whitlock
August 31, 2012 19:09 PM

Short answer:

My recomendation is adding this methods the image class:

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

The Image object is still inmutable, these methods return a new image.

Tulains Córdova
Tulains Córdova
October 03, 2012 15:01 PM

I think in the "Image Domain", you just have the Image object which is immutable and monadic. You ask the image for a resized version and it returns a resized version of itself. Then you can decide whether you want to get rid of the original or keep both.

Now the thumbnail, avatar, etc versions of the Image is another Domain entirely, which can ask the Image domain for different versions of a certain image to give to a user. Usually this domain is also not so huge or generic, so you can probably keep this in application logic.

In a small scale app, I would resize the images at read time. For example I could have a apache rewrite rule that delegates to php a script if the image 'http://my.site.com/images/thumbnails/image1.png', where the file will be retrieved using the name image1.png and resized and stored in 'thumbnails/image1.png'. Then on the next request to this same image, apache will serve the image directly without running the php script. Your question of findAllImagesWithoutThumbnails is automatically answered by apache, unless you need to do statistics?

In a large scale app, I would send the all new images to a background job, which takes care of generating the different versions of the image and saves it in appropriate places. I wouldn't bother creating a whole domain or class as this domain is very unlikely to grow into a terrible mess of spaghetti and bad sauce.

andho
andho
October 25, 2012 11:08 AM

Related Questions


Finding object relationships

Updated November 28, 2017 13:05 PM


Domain model design

Updated July 23, 2016 08:02 AM

DDD Injecting Services on Entity Methods Calls

Updated September 25, 2017 01:05 AM

Primitive vs Class to represent simple domain object?

Updated October 30, 2017 07:05 AM