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.51There 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.51Despite 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