Tuesday, December 1, 2015

Dart Has Friend Class Baked In


The great thing about blogging every day is that I'm guaranteed to make an idiot out of myself. There is just no way around it. There are some days (sometimes even several days in succession) when a obvious solution just isn't obvious. And it's very liberating to accept that and just move forward with learning.

Actually, it's still completely embarrassing, but I'll stick with the first story.

Today's embarrassment comes in the form friend classes in Dart. I continue to explore the memento pattern for the forthcoming Design Patterns in Dart. The implementation section in the Gang of Four book suggests that, to preserve encapsulation, the memento should expose its properties only to the originating class of the pattern, but not to anything else.

Specifically, the caretaker that saves memento objects should not itself be able to access the properties of the memento. Since the memento represents a previous internal state, the caretaker should not be looking under the covers, so to speak. If it needs to know anything, it should ask the originating class. In the Gang of Four book, this is achieved via friend classes.

For the past two nights, I have struggled to bend Dart to my will in achieving this friend class behavior. Instead, as the esteemed James Hurford pointed out in last night's comments, Dart's libraries simply work this way.

Private variables in Dart are private to a library, not a class. So if I make the properties of my memento object private, nothing outside of the library (like the caretaker from the pattern) can access them. But the originator class in the same library should still be able to access them.

The originating class with which I have been experimenting is the VelvetFogMachine, which plays wonderful selections of the great crooner's catalog. The nowPlaying getter method is used to generate the Playing memento objects:
class VelvetFogMachine {
  Song currentSong;
  double currentTime;
  // ...

  // Create a memento of the current state
  Playing get nowPlaying => new Playing(currentSong, time);
  // ...
}
Following James' suggestion, I should make the properties of Playing private (leading underscore variables are private in Dart):
// The Memento
class Playing {
  Song _song;
  double _time;
  Playing(this._song, this._time);
}
Next, I ensure that both of these classes exist in the velvet_fog_machine library:
library velvet_fog_machine;
// The "Originator"
class VelvetFogMachine { /* ... */ }
// The Memento
class Playing { /* ... */ }
With that, the VelvetFogMachine should have access to Playing's _song and _time private variables when restoring a previous memento:
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);
  }
}
And that actually works right away. Well, I shouldn't say "actually." I really should have remembered that this is how private variables work, but this is so much simpler than any of the craziness that I had been trying the previous two nights.

Back in the caretaker code—and outside the velvet_fog_machine library—I can play songs, store mementos, and restore mementos:
  import 'package:memento_code/velvet_fog_machine.dart';
  // ...
  List<Playing> replayer = [];

  var scatMan = new VelvetFogMachine();
  // ...
  scatMan.play(
      'New York, New York Medley',
      'A Vintage Year'
  );
  replayer.add(scatMan.nowPlaying);

  scatMan.play(
      'The Lady is a Tramp',
      'The Velvet Frog: The Very Best of Mel Tormé'
  );

  // The New York, New York Medley with George Shearing really is wonderful
  scatMan.backTo(replayer.last);
Running this from the command line produces the usual play() messages and verifies that the backTo() method was able to access the memento's private properties:
./bin/play_melvie.dart
Playing Blue Moon // The Velvet Frog: The Very Best of Mel Tormé @ 0.00
Playing 'Round Midnight // Tormé @ 0.00
Playing It Don't Mean A Thing (If It Ain't Got That Swing) // Best Of/ 20th Century @ 0.00
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 @ 1.28
Best of all, if I try to access the private variables from the caretaker:
    print("Caretaker says last remembered song is: ${replayer.last._song}");
Then I get no-such-method errors and dartanalyzer complains:
[warning] The getter '_song' is not defined for the class 'Playing' (/home/chris/repos/design-patterns-in-dart/memento/bin/play_melvie.dart, line 40, col 68)
This is exactly the "friend class" behavior that I wanted.

So the moral of the story is that it takes two days of trying dumb stuff on the internet before a kind soul takes pity on you and points you in the right direction. No wait, the moral of the story is that if you want friend classes in Dart, keep them in the same library. Thanks James!


Day #20

3 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. I didn't realise that members of a library were friends with each other either till last night. I just had a bunch that there was a better way.

    ReplyDelete