Sunday, November 29, 2015

Making Friends in Dart


The memento in my memento pattern is a tad friendly. In the original Gang of Four book, the suggested implementation has a narrow interface into the memento class. That is, only the originator of the memento can access the memento's properties.

My Dart version is considerably more open:
// The Memento
class Playing {
  Song song;
  double time;
  Playing(this.song, this.time);
}
I continue to work on the VelvetFogMachine application. It needs access to the auto-defined song and time getter methods when restoring a previously playing Mel Tormé song:
// The "Originator"
class VelvetFogMachine {
  // ..
  // Restore from memento
  void backTo(Playing p) {
    print("  *** Whoa! This was a good one, let's hear it again :) ***");
    _play(p.song, p.time);
  }
}
Really, all that the originator needs to access are the getters as shown. I do not need the auto-defined setters. I could address this with private properties paired with explicit, public getters:
class Playing {
  Song _song;
  double _time;
  Playing(this._song, this._time);

  Song get song => _song;
  double get time => _time;
}
This helps a little to address concerns with encapsulation. It is still possible for the caretaker context, which should only play or restore songs (set / restore state), to access the song and time properties. In most cases, this will not be a problem. The caretaker will merrily play and restore without ever wondering about the memento returned by nowPlaying:
  scatMan.play(
      'New York, New York Medley',
      'A Vintage Year'
  );
  replayer.add(scatMan.nowPlaying);
  //  Play other songs...

  // The New York, New York Medley with George Shearing really is wonderful
  scatMan.backTo(replayer.last);
That said, there is virtue in narrowing the interface. Another coder might come along and attempt to get access the memento song title. To achieve that, encapsulation would need to be broken, violating the very intention of the memento pattern.

So what is to be done here? Unfortunately, I cannot think of much in Dart where there is no support for friend classes (as far as I know). The best I can come up with is an ugly hack to determine the caller of a noSuchMethod() invocation.

In Dart, if a method is not found, but the class declares a noSuchMethod(), then that noSuchMethod() method is invoked. I could rewrite the public getters for song and time as:
class Playing {
  Song _song;
  double _time;
  Playing(this._song, this._time);

  noSuchMethod(Invocation i) {
    if (i.memberName == #song) return this._song;
    if (i.memberName == #time) return this._time;

    return super.noSuchMethod(i);
  }
}
That is fairly straight-forward. If the noSuchMethod() method is invoked with song as the method name, then the private _song property is returned. The same thing happens with time. If something else if invoked, then the superclass noSuchMethod() is invoked, likely throwing a halting exception.

This does nothing to prevent classes other than VelvetFogMachine from accessing song and time. For that, I need access to the class that invoked the getter. As far as I know there is no "real" way to accomplish this in Dart. The best I can do is throw an exception and catch the stacktrace:
class Playing {
  Song _song;
  double _time;
  Playing(this._song, this._time);

  noSuchMethod(Invocation i) {
    try { throw new Error(); }
    catch (_exception, stackTrace) {
      if (!stackTrace.toString().contains(new RegExp(r'\#1\s+VelvetFogMachine'))) {
        return super.noSuchMethod(i);
      }
    }

    if (i.memberName == #song) return this._song;
    if (i.memberName == #time) return this._time;

    return super.noSuchMethod(i);
  }
}
That works and dartanalyzer doesn't complain (with a @proxy annotation applied to the class). But yuck. Further, I an unsure if this works in all Dart VMs (it does not work on DartPad, for instance).

So, unless someone has any ideas, it seems safe to say that Dart classes are friendly to everyone. In other words, Dart classes are slightly too friendly for the memento pattern.


Day #18

4 comments:

  1. I'm not sure I agree with that last statement, about Dart classes being to friendly for the memento pattern. All that the memento pattern was designed to do was give the ability to save and then later on restore the state. In other words, do something similar to undo. Being friendly or not doesn't affect this pattern, as it's just a pattern. Unless I'm missing the point you're trying to get across here.

    ReplyDelete
  2. Thinking about this further, I think a state can be private to a class by using an _ at the start of the field name. You can create methods to return a memento representing the state then later on restoring this state with a restore method with the memento as it parameter. That would satisfy the requirement of encapsulation. In fact that's pretty much the way the examples for this pattern do it. Even if the Dart class is open and friends with everyone, still doesn't mean the pattern isn't useful for retrieving then later on restoring a state.

    ReplyDelete
    Replies
    1. Apologies. My last statement is a bit broad. It is a memento pattern and one that is typically shown in most languages. Nevertheless, the GoF specifically mentioned a narrow interface on the memento object for all contexts except the originator class. Maybe it's a small point, but it's the kind of thing I have no choice but to explore :)

      Delete
    2. I was wondering. Thanks for the clarification of what you're doing.

      Delete