gmosx.ninja

Protected members in Dart classes

In general, Dart is a well designed language, but there are some rough edges. Take for example the limited privacy options for class members: Dart only provides for private members where the boundary of privacy is the library.

While private members can get you a long way, often, what you are really looking for is a protected member, i.e. a private member that is still visible to sub-classes even if they are defined in different libraries.

I guess, the canonical example is a UI framework offering e.g. a Button class. When extending the base class to create your own FancyButton, access to 'private' implementation details is often required. Lack of protected access forces you to expose the implementation through public members.

Thankfully, there is a simple workaround. I will demonstrate with an example, leveraging the above-mentioned UI framework scenario. Let's start by defining a base Button class:

library ui.button;

// 'Public' API
abstract class Button {
  factory Button({HtmlElement host}) = ButtonImpl; // Redirecting constructor
  void click();
}

// 'Protected' implementation
abstract class ButtonImpl implements Button {
  ButtonImpl({this.host});

  int state = 0; // 'protected' implementation detail.

  @override
  void click() {
    this.state = 1;
    host.classes.add('selected');
  }
}

In your application, name-spaced in another library, you define a custom button:

library my.app.fancy_button; // notice, this is another library

abstract class FancyButton extends Button {
  factory FancyButton({String title, HtmlElement host}) = FancyButtonImpl;
}

class FancyButtonImpl extends ButtonImpl implements FancyButton {
  String title; // voila, a protected property

  ButtonImpl({this.title, HtmlElement host}) : super(host: host);
}

You can use it like this:

final button = new FancyButton(title: "Cancel", host: element);

button.title // => cannot access!
button.state // => cannot access!

You cannot access the 'protected' members of FancyButton (or even Button's protected members, for that matter). As an added bonus, if you would like to side-step the protection (e.g. you are writing a unit-test and you would like to assert some implementation details) you can just instantiate the implementation class:

final button = new FancyButtonImpl(title: "Cancel", host: element);

expect(button.title, equals("Cancel")); // => true

While this workaround is relatively straightforward, I really hope Dart's designers will introduce a @protected annotation in the future. An annotation that is statically asserted (e.g. by the IDE or dart-analyzer) would be an elegant and practical solution.