Friday, January 15, 2016

Virtual Proxy Pattern in Dart


Tonight I look at the virtual flavor of the proxy pattern. The Java example on Wikipedia seems a fine place to start. Hopefully this works better than the remote-in-Dart-isolates flavor of the past few days.

I start with the Image interface, which declares that all classes that implement it will sport a displayImage() method:
abstract class Image {
  void displayImage();
}
For this simple example, the RealImage class will have print-to-stdout placeholder methods for actions. The real image needs to be able to load itself from the filesystem when constructed and to display itself when required:
class RealImage implements Image {
  String _filename;

  RealImage(this._filename) {
    _loadImageFromDisk();
  }

  void _loadImageFromDisk() {
    print("Loading    $_filename");
  }

  void displayImage() {
    print("Displaying $_filename");
  }
}
That is simple enough, but it is a nice part of the example. This real subject of the pattern performs an expensive operation when created (or it pretends to). In this case, RealImage loads a large image file from the filesystem.

The project subject, on the other hand, delays expensive operations a long as possible. When created, it stores a reference to the _filename, but only constructs the real image when it has to—when the image needs to display:
class ProxyImage implements Image {
  RealImage _image;
  String _filename;

  ProxyImage(this._filename);

  void displayImage() {
    _image ??= new RealImage(_filename);
    _image.displayImage();
  }
}
The assign-if-null operator (??=) in displayImage() also maintains a reference to the real image should the image need to be displayed a second time.

That is all there is to the basic, virtual proxy. The "virtual" in the name comes from the behavior of the proxy subject—it is very close to behaving in the same manner as the real subject. Aside from the on-demand nature, the two are nearly the same.

And this works as expected. The following main entry point into the code:
main() {
  Image image1 = new ProxyImage("HiRes_10MB_Photo1");
  Image image2 = new ProxyImage("HiRes_10MB_Photo2");

  image1.displayImage(); // loading necessary
  image1.displayImage(); // loading unnecessary
  image2.displayImage(); // loading necessary
  image2.displayImage(); // loading unnecessary
  image1.displayImage(); // loading unnecessary
}
Produces the following output:
$ ./bin/display_images.dart             
Loading    HiRes_10MB_Photo1
Displaying HiRes_10MB_Photo1
Displaying HiRes_10MB_Photo1
Loading    HiRes_10MB_Photo2
Displaying HiRes_10MB_Photo2
Displaying HiRes_10MB_Photo2
Displaying HiRes_10MB_Photo1
I will probably make a go of implementing that for real tomorrow. To better simulate a real image gallery, I might first use a nearly zero-cost blank image:
final BlankImage = new RealImage("blank");
The proxy can display that immediately, then load a not-too-much-higher-cost low resolution version of the image before finally displaying the real thing:
class ProxyImage implements Image {
  // ...
  void displayImage() {
    if (_image != null) return _image.displayImage();

    print("[ProxyImage] cache miss $_filename");
    BlankImage.displayImage();
    new RealImage(_lowFilename)..displayImage();
    _image = new RealImage(_filename)..displayImage();
  }

  String get _lowFilename =>
    _filename.replaceFirst(new RegExp(r'HiRes_\d+\w+_'), 'LoRes_');
}
Running that now produces:
[ProxyImage] cache miss HiRes_10MB_Photo1
Loading    blank
Displaying blank
Loading    LoRes_Photo1
Displaying LoRes_Photo1
Loading    HiRes_10MB_Photo1
Displaying HiRes_10MB_Photo1
Displaying HiRes_10MB_Photo1
[ProxyImage] cache miss HiRes_10MB_Photo2
Displaying blank
Loading    LoRes_Photo2
Displaying LoRes_Photo2
Loading    HiRes_10MB_Photo2
Displaying HiRes_10MB_Photo2
Displaying HiRes_10MB_Photo2
Displaying HiRes_10MB_Photo1
Each low resolution image is loaded only once, acting as a second (though more helpful) placeholder after the initial blank image. But once the full resolution image is available, it is used going forward. I will give that a go with real images tomorrow. For now...

Play with the code on DartPad: https://dartpad.dartlang.org/22ba8996f50d61cea887.


Day #65

No comments:

Post a Comment