Saturday, January 16, 2016

A Real Virtual Proxy Pattern


Up today, I would like to make my virtual proxy pattern in Dart a little more tangible. Concrete if you will.

Yesterday's example copied shamelessly from the image loading example on the Wikipedia page. Today, I modify it so that, instead of placeholder print() statements, I load actual images on to a page. The context of the image loading proxy pattern is an image gallery. Now, image galleries are the simplest things in the world. Until they aren't.

Consider a personal image collection of 10,000 photos. When the page first loads, it makes sense to load the most recent 100 or so images for display. This is a nice balance between immediate feedback and swamping bandwidth and browser resources. Problems occur if the user immediately scrolls back a few years. The gallery should stop loading those initial images and get to work on the first 100 from a few years back. But if the user missed the date by a month or two, then those images should stop loading and the next batch should start. It turns out to be an interesting challenge, and one that the proxy pattern can help address.

For this exercise, I look at individual images in the gallery. When the image first comes into view, it should display a blank image. After a brief pause (to allow other resources to stop and to ensure the user didn't start scrolling again), the image should load a low resolution version of itself. Lastly, it should load a high resolution version of the photo.

Since there are going to be delays and waiting for loading, I changes the Image interface to return a future when displaying the image:
abstract class Image {
  Future displayImage();
}
The RealImage, which will be the real subject in the pattern, remains relatively unchanged from last night. It is constructed with a filename and then loads that image. Instead of last night's placeholder print() statement, tonight I construct an HTML image element:
class RealImage implements Image {
  String _filename;
 ImageElement img;
  
  RealImage(this._filename) {
    _loadImage();
  }

  void _loadImage() {
    print("Loading    $_filename");
    img = new ImageElement(src: _filename, width: 800, height: 446);
    img.style.border = '1px dashed grey';
    document.body.append(img);
  }
  // ...
}
As for diplayImage(), I still leave this a little rough for ease of illustration. It allows for the _filename to have been changed, updating the ImageElement's src attribute as a means of displaying the image:
lass RealImage implements Image {
  // ...
  Future displayImage() {
    print("Displaying $_filename");
    img.src = _filename;
    return new Future.value();   
  }
}
Were I doing this for real, I would get the Future from a Completer that completes when the image loads. But this will do for now.

Now for the ProxyImage class. I would like to construct this with three filenames / URLs: the tiny version of the image, the low resolution version of the image, and the high resolution of the image. To load the UML diagrams of the proxy pattern from the Wikipedia page, for instance, the ProxyImage creation might look like:
  Image image = new ProxyImage(
    'https://upload.wikimedia.org/wikipedia/commons/5/52/Spacer.gif',
    'https://upload.wikimedia.org/wikipedia/commons/thumb/7/75/Proxy_pattern_diagram.svg/320px-Proxy_pattern_diagram.svg.png',
    'https://upload.wikimedia.org/wikipedia/commons/thumb/7/75/Proxy_pattern_diagram.svg/1280px-Proxy_pattern_diagram.svg.png'
  );
To support that, I define three instance variables and require them in the constructor:
class ProxyImage implements Image {
  RealImage _image;
  String _tiny, _lo, _hi;

  ProxyImage(this._tiny, this._lo, this._hi);
  // ...
}
To put a little delay in between loading the different resolutions, I define a simple _pause() helper method:
class ProxyImage implements Image {
  // ...
  Future _pause(int s) {
    var c = new Completer();
    new Timer(new Duration(seconds: s), c.complete);
    return c.future;
  }
}
Given a number, this method returns a future that completes in that number of seconds. With that, I can declare the displayImage() method:
class ProxyImage implements Image {
  // ...
  Future displayImage() async {
    if (_image != null) return _image.displayImage();

    // Start with 1-byte blank image
    _image = new RealImage(_tiny)..displayImage();
    await _pause(1);
    
    // Load the low-res version
    _image
      .._filename = _lo
      ..displayImage();
    await _pause(5);
    
    // Then the hi-res version
    _image
      .._filename = _hi
      ..displayImage();
    return new Future.value();
  }
  // ...
}
Since this is the proxy pattern, this proxy class forwards the proxy method displayImage() to the real subject. In this example, it does so three times for the tiny, low resolution, and high-resolution versions of the image.

I again use the async / await syntax to clean up my Future code here. And clean up it does. After loading the first, tiny image, I await a pause of one second. Then, after loading the second, low resolution image, I await a pause of five seconds. Once that pause is done, the high resolution version of the real image is loaded. Again, I might return a future that completes when the image is loaded, but keep it simple by returning a Future that completes immediately.

And that does the trick. The full, working version of the code is available on DartPad: https://dartpad.dartlang.org/851fe3543e94ec4bb5c0.

When the code is first run, a blank image is displayed. A second later, the low resolution version of the proxy pattern UML diagram displays. Then, 5 seconds after that, the full version displays. If this were part of an image gallery, the ProxyImage instances could be staggered to load at slightly different times from each other. The timers that pause the next higher resolution version of the image could be canceled if the user scrolls the image out of the viewport. All in all, the proxy pattern seems a neat solution for many of the concerns introduced by image galleries.


Day #66

No comments:

Post a Comment