My memento has a wide interface. I continue to work with the memento pattern in Dart. The initial implementation went well, but the devil is in the details.
The details in this case come in the form of implementation item #1 from the Gang of Four book, which suggest that only the originator class should have access to the properties of the memento object. As I found last night, that is not an easy thing to do in Dart. There is no concept of friend classes in Dart. There is not even an easy way to obtain the caller of a function to determine access.
In the end, I hacked a caller into the memento object in my example code by examining the stacktrace of a thrown exception. There is no world in which that is a good idea, so tonight, I try to do a little better. Since neither the requirements nor the limitations have changed, I have to try an alternate approach. And I think it all has to be done at runtime.
The originator class in my example is the
VeletFogMachine
, which plays various songs from the late, great Mel Tormé. The memento class is Playing
, which the caretaker context can use to replay previous songs. Again, the caretaker should not be able to access any song information—only supply the Playing
memento to the originator so that it can be handled properly.What I come up with is a runtime secret, supplied to by the
VelvetFogMachine
:class Playing { Song _song; double _time; double _secret; // Narrow interface for the world Playing(); // Wide interface for the VelvetFogMachine Playing.forTheVelvetFogMachine(this._song, this._time, this._secret); // ... }This named constructor suggests that it only works with the
VelvetFogMachine
. In reality, it is possible to use it from outside that context, but it ought to feel really strange to just about anyone.The secret supplied to the constructor can then be used to check access to a particular method, like the
secretSong()
method:class Playing { // ... Song secretSong(double secret) { // Runtime check for access _checkAccess(secret, #secretSong); return _song; } // ... _checkAccess(secret, memberName) { if (secret == _secret) return; throw new NoSuchMethodError(this, memberName, [secret], {}); } }The secret itself has to come from the
VelvetFogMachine
originator class and it has to be private to that class so that no other classes can get access:import 'dart:math'; final rand = new Random(1); // The "Originator" class VelvetFogMachine { Song currentSong; double currentTime; final double _secret = rand.nextDouble(); // ... // Create a memento of the current state Playing get nowPlaying { // Simulate playing at some time later: var time = 5*rand.nextDouble(); return new Playing.forTheVelvetFogMachine(currentSong, time, _secret); } // ... }That works. The original code continues to function properly when used inside the originator:
class VelvetFogMachine { // ... void backTo(Playing p) { print(" *** Whoa! This was a good one, let's hear it again :) ***"); _play(p.secretSong(_secret), p.secretTime(_secret)); } }Furthermore, the caretaker code is unable to access the
secretSong()
method:List<Playing> replayer = []; var scatMan = new VelvetFogMachine(); // ... scatMan.play( 'New York, New York Medley', 'A Vintage Year' ); replayer.add(scatMan.nowPlaying); // ... // This should not work: try { print("Caretaker says last remembered song is: ${replayer.last.secretSong(0.0)}"); } on NoSuchMethodError { print("Yay! Caretaker was denied access to the memento song."); }This results in the
NoSuchMethodError
output:$ ./bin/play_melvie.dart ... Playing New York, New York Medley // A Vintage Year @ 0.00 Playing The Lady is a Tramp // The Velvet Frog: The Very Best of Mel Tormé @ 0.00 *** Whoa! This was a good one, let's hear it again :) *** Playing New York, New York Medley // A Vintage Year @ 4.60 Yay! Caretaker was denied access to the memento song.So this works, but it is an awful lot of work and there are still ways around it. Still, unless someone has a suggestion, I will likely stick with this as the best approach to narrowing the interface of the memento when discussing in Design Patterns in Dart.
Play with this code on DartPad: https://dartpad.dartlang.org/a1971dcad8f0466d7cfe
Day #19
I have a possible better solution to do with libraries and visibility. Pretend the Player is in a separate library from the caller and you'll see how this might work.
ReplyDeletehttps://dartpad.dartlang.org/51c3a67c1b7c858f0555
To clarify what I'm getting at, any class in a library are friends with each other. Any class outside the library is not a friend. A friend class can see the privates of that class, so any classes within a library can see private vars in a class instance of a class within the same library. This gives you a solution that is similar to the one stated for use when doing it in C++. Hope I've explained things better, and haven't further confused things. If you've used Java before, you'd see the similarity, since I that's the way Java handles it as well, from within the same package.
ReplyDeleteThat makes perfect sense. Blog post coming based on that in a bit. Thanks for saving me!
DeleteYes, I found this out thanks to the book, Dart up and running by Kathy Walrath and Seth Ladd
Delete