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_Photo1I 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_Photo1Each 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