Saturday, November 28, 2015

Memento Not As State


I really need to cut out the extreme late night coding. I left off last night with a fully formed planned to increase the "Dartiness" in my memento pattern example, but in the light of day I haven't the velvet foggiest notion what that plan might have been. No pity here though, there is still something to say about the existing code.

I am curious about the relationship between the internal state of the originator and the memento object. In particular, should the memento object be the state in the originator class? After last night, that is exactly what the VelvetFogMachine originator class does in its play() method:
// The "Originator"
class VelvetFogMachine {
  // The Memento
  Playing _nowPlaying;

  // Set the state
  void play(String title, String album, [double time = 0.0]) {
    print("Playing $title // $album @ ${time.toStringAsFixed(2)}");
    _nowPlaying = new Playing(title, album, time);
  }
  // ...
}
The second time around with the code, I think perhaps the memento-as-state is inappropriate. Oh, it works—especially in this short example. In real life code however, it is more likely that the VelvetFogMachine would want to track the current song and current time directly. Something along the lines of the following achieves a nearness to a more organic VelvetFogMachine:
class VelvetFogMachine {
  Song currentSong;
  double currentTime;

  void play(String title, String album, [double time = 0.0]) {
    currentSong = new Song(title, album);
    currentTime = time;
    print("Playing $currentSong @ ${time.toStringAsFixed(2)}");
  }
  // ...
}
My romance of a better example would push the song title and album into a new Song class:
class Song {
  String title, album;
  Song(this.title, this.album);
  String toString() => "$title // $album";
}
Ah, Dart code, do I love you because you're beautiful? You certainly know my heart, but I still do not believe that is the "Dartiness" I was planning last night. Hopefully I can figure it out because it's really under my skin.

Back to my example, the currentSong and currentTime feel like properties that could exist before realizing that a memento pattern was needed. In other words, I think this is a stronger memento example. Now what to do with the memento class? Previously, it stored separate song title, album and time properties:
class Playing {
  String title, album;
  double time;
  Playing(this.title, this.album, this.time);
}
I am tempted to stick with with this structure in the memento class. After all, it mirrors the signature of the play() method in the VelvetFogMachine's play() method that creates the internal state being memento-ized. In the end, I think it best for the memento class to mirror the internal structure of the originator class, so title and album are replaced with song:
class Playing {
  Song song;
  double time;
  Playing(this.song, this.time);
}
Now that I have settled on this for the Playing memento, the method that creates the memento in VelvetFogMachine is trivial. The nowPlaying getter method simply instantiates the memento with the current song and time:
class VelvetFogMachine {
  // ...
  Playing get nowPlaying => new Playing(currentSong, currentTime);
  // ...
}
Everyday's a holiday with Dart and hash-rocket return methods. That's good, clean fun.

But what of the method that restores state from a previously recorded memento? It now has to translate the Song-based memento into the three argument play() method:
class VelvetFogMachine {
  // ...
  void backTo(Playing p) {
    print("  *** Whoa! This was a good one, let's hear it again :) ***");
    play(p.song.title, p.song.album, p.time);
  }
}
Some indirection aside, that is not too horrible. Certainly it is better that the VelvetFogMachine be responsible for translating a memento of previous state into a play() instead of the caretaker context. The whole point of the memento after all, is to prevent exposing internal implementation details when saving state.

If the indirection bothers me, I can replace the current play() with a call to a private, Song-based method:
  void play(String title, String album, [double time = 0.0]) {
    _play(new Song(title, album), time);
  }

  void _play(Song s, double t) {
    currentSong = s;
    currentTime = t;
    print("Playing $currentSong @ ${currentTime.toStringAsFixed(2)}");
  }
With that, the restore-from-memento method can invoke the same _play() method:
  void backTo(Playing p) {
    print("  *** Whoa! This was a good one, let's hear it again :) ***");
    _play(p.song, p.time);
  }
I think I prefer that approach. That said, I might stick with the previous, non-private-based play() method in the book. The difference between the three argument play() method and the two-property memento better illuminates the need to hide implementation details of the originator from the outside world.

That's about all for this bit of exploration. I am clearer on the need for the memento to encapsulate, but not be, the internal state of the originator. I had been glossing over that in my mind, but am happy for having cracked that chestnut.

And I do think I remember that "Dartiness" factor I had hoped to explore. Hopefully we are not strangers in the late night and I will still remember it tomorrow. Stay tuned Mel fans!

Play with this code on DartPad: https://dartpad.dartlang.org/35e0056a1f290c74d996

Day #17

No comments:

Post a Comment