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
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.
ReplyDeleteThinking 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.
ReplyDeleteApologies. 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 :)
DeleteI was wondering. Thanks for the clarification of what you're doing.
Delete