Wednesday, December 2, 2015

Incremental Mementos


Let's have some fun with incremental mementos. I have a decent memento pattern example ready for Design Patterns in Dart. Hopefully it can be adapted to produce incremental memento objects instead of the full state object that it is currently returning.

The example application is the velvet fog machine, used by lover's of Mel Tormé everywhere to produce the perfect playlist for any occasion. The originator class in the pattern is the VelvetFogMachine, which serves as a digital jukebox. It plays songs:
class VelvetFogMachine {
  // ...
  void play(String title, String album, [double time = 0.0]) {
    _play(new Song(title, album), time);
  }
  // ...
}
It can send back mementos of the current song / time within the song:
class VelvetFogMachine {
  // ...
  Playing get nowPlaying => new Playing(currentSong, currentTime);
  // ...
}
It can use these Playing mementos to return to previous states:
class VelvetFogMachine {
  // ...
  void backTo(Playing p) {
    print("\n===> (backTo)");
    _play(p._song, p._time);
  }
}
Currently, that Playing class stores both the currentSong and currentTime every time that the nowPlaying getter is invoked in VelvetFogMachine. But why bother doing that if the song stays the same while the time increases:
  List<Playing> replayer = [];

  var scatMan = new VelvetFogMachine();
  scatMan.play(
      'New York, New York Medley',
      'A Vintage Year'
  );
  replayer.add(scatMan.nowPlaying);
  replayer.add(scatMan.nowPlaying);
  replayer.add(scatMan.nowPlaying);
  replayer.add(scatMan.nowPlaying);

  // Now move back to the different bookmarked times in the 
  // New York, New York song
  scatMan.backTo(replayer.removeLast());
  scatMan.backTo(replayer.removeLast());
  scatMan.backTo(replayer.removeLast());
  scatMan.backTo(replayer.removeLast());
The result of running this code looks something like:
Playing New York, New York Medley // A Vintage Year @ 0.00

===> (backTo)
Playing New York, New York Medley // A Vintage Year @ 5.10

===> (backTo)
Playing New York, New York Medley // A Vintage Year @ 4.22

===> (backTo)
Playing New York, New York Medley // A Vintage Year @ 2.35

===> (backTo)
Playing New York, New York Medley // A Vintage Year @ 0.51
There really is no need to store the song in each memento. But where should the responsibility lie for remembering the previous memento, determining the difference between increments?

I think it has to be the originator—the VelvetForMachine. One way or another, the VeletFogMachine needs to know what to do when it sees a null song in backTo():
  void backTo(Playing p) {
    print("\n===> (backTo)");
    var song = (p._song == null) ? currentSong : p._song;
    _play(song, p._time);
  }
So if the VelvetFogMachine is handling increments when restoring state, it stands to reason that it should do the same when creating state:
  Playing get nowPlaying {
    var song  = (_lastSong == currentSong) ? null : currentSong;
    _lastSong = currentSong;

    return new Playing(song, currentTime);
  }
Introduced here is _lastSong to track which song was playing the last time state was recorded in a memento. If the last song and the current song are the same, then the memento records a null song. Otherwise the actual song is recorded to note the change in songs.

That seems to work. When the code is run, the output is reported as:
Playing New York, New York Medley // A Vintage Year @ 0.00
...

===> (backTo)
Playing New York, New York Medley // A Vintage Year @ 5.10

===> (backTo)
Playing New York, New York Medley // A Vintage Year @ 4.22

===> (backTo)
Playing New York, New York Medley // A Vintage Year @ 2.35

===> (backTo)
Playing New York, New York Medley // A Vintage Year @ 0.51
Despite the seemingly working code, I have some reservations about this—at least about including it in the book. I am faking the times with a random increment. That does not seems conceptually hard for full state, but I worry that it will be confusing should I choose to discuss it in the book. Perhaps I can simply gloss over incremental changes as did the Gang of Four book.

I also have the feeling that this incremental code really ought to move out into its own class. I left it in the VelvetFogMachine as I explored, but if the rest of the pattern is obsessed with single-responsibility, then it seems like this is asking for trouble.

All that said, I may investigate this further. Just because the GoF did not discuss incremental changes in detail does not give me warrant to do the same. If I can do so easily and if it helps support discussion of the pattern, I am obligated to pursue it. Tomorrow.

Play with this code on DartPad: https://dartpad.dartlang.org/4bde9f50dfd2dea2c872.


Day #21

No comments:

Post a Comment