Friday, December 4, 2015

Serializing Mementos: A Good Idea?


I'm pretty the answer is going to be "don't."

Still, I would like to explore serializing the memento object in the memento pattern for offline storage. The whole point of the memento pattern is to facilitate restoring state without allowing access to that state. This exercise may come down to semantics, but let's try it out first.

Currently, I am working with my Dart VelvetFogMachine object, which aids in the search for the ultimate Mel Tormé playlist. This is the originator in the memento pattern. In the caretaker code, I keep a list of saved state and work with this VelvetFogMachine:
  // List of mementos
  List replayer = [];

  // The originator
  var scatMan = new VelvetFogMachine();
This caretaker code cannot see inside the memento—and thus cannot access the internal state of the VelvetFogMachine. It can tell the machine to play music and, at particularly moving moments of playback, request a bookmark so that we can return to this point in the current song:

  // ...
  scatMan.play(
      '\'Round Midnight',
      'Tormé'
  );
  replayer.add(scatMan.nowPlaying);
  // ...
  scatMan.play(
      'New York, New York Medley',
      'A Vintage Year'
  );
  replayer.add(scatMan.nowPlaying);
  // ...
The play() sends the currently playing song to STDOUT, so running this code results in a report similar to:
$ ./bin/play_melvie.dart
Playing 'Round Midnight // Tormé @ 0.00
...
Playing New York, New York Medley // A Vintage Year @ 0.00
...
And, when the VelvetFogMachine is sent back to a memento:
  // Restore a memento
  scatMan.backTo(replayer.last);
It is reported as:
  *** Whoa! This was a good one, let's hear it again :) ***
Playing New York, New York Medley // A Vintage Year @ 1.27
So things work well and encapsulation is maintained. There is no way for the caretaker to get at the current state of the VelvetFogMachine(). All interaction goes through the play() method which is then solely responsible for updating the internal state of things. By keeping the VelvetFogMachine interface narrow like this, it will be more reliable and is more extensible.

So what about storing the replayer list of mementos offline for later restore? Will something like the following break encapsulation?
  var saved = serializePlaylist(replayer);
  // Then, some time later...
  var restored = deserializePlaylist(saved);
  scatMan.backTo(restored.first);
To answer that question, I start in the velvet_fog_machine library by importing the dart:convert library so that the object can be serialized into JSON:
library velvet_fog_machine;

import 'dart:convert';
//... 
The memento object itself, which describes the currently playing song, will need to support a toJson() method to work with the JSON.encode() method:
// The Memento
class _Playing {
  Song _song;
  double _time;
  _Playing(this._song, this._time);
  Map toJson() => {'song': _song.toJson(), 'time': _time};
}
That makes the serializePlaylist() helper function quite straight-forward. I can simply call JSON.encode on the list of _Player mementos and dart:convert does the rest:
String serializePlaylist(List<_Playing> list) => JSON.encode(list);
As for getting the memento back, that is a little harder. This is where the knowledge of the underlying implementation is needed. I have to decode the JSON list of mementos, then map each item in the list into a _Playing object:
List<_Playing> deserializePlaylist(String json) {
  return JSON.decode(json).map((p) {
    var time = p['time'];
    var song = new Song(p['song']['title'], p['song']['album']);
    return new _Playing(song, time);
  });
}
With that, the serialize/deserialize code in the caretaker:
  var saved = serializePlaylist(replayer);
  var restored = deserializePlaylist(saved);
  scatMan.backTo(restored.first);
Will produce:
  *** Whoa! This was a good one, let's hear it again :) ***
Playing New York, New York Medley // A Vintage Year @ 1.27
--
  *** Whoa! This was a good one, let's hear it again :) ***
Playing 'Round Midnight // Tormé @ 2.89
So it works, but does it violate the either the letter or the spirit of the memento pattern law?

I would argue that it does not violate the spirit of the law. All of the internal knowledge of the implementation details remains in the velvet_fog_machine library. The caretaker code still maintains the list of mementos. And it still cannot access the internal state of the VelvetFogMachine.

That said, it could get access to the internal state and some idea of the implementation by looking at the serialized mementos in the list:
[{"song":
   {"title":"'Round Midnight","album":"Tormé"},
 "time":2.89},
{"song":
   {"title":"New York, New York Medley","album":"A Vintage Year"},
"time":1.27}]
Ultimately, I think this is probably OK-ish. Even armed with with this knowledge, the caretaker code still cannot access the actual data in the memento. It is still forced to work through the play() method to interact with the internal state of the originator. Some details are exposed, but not in a manner in which the caretaker can affect the originator. So the originator retains encapsulation and the code remains maintainable.

So the answer would seem to be, "do". But only if necessary.

Play with this code on DartPad: https://dartpad.dartlang.org/69bc9464388fadee2042.


Design Patterns in Dart


Day #23

No comments:

Post a Comment