1

I have an abstract class that adds strings to a list and does some other stuff.

abstract class Abc {
  final _list = <String>[];
  Function(String) addedValue;

  void add(String s) {
    _list.add(s);
    if (this.addedValue != null) this.addedValue(s);
  }
}

In my subclass I want a callback every time a string is added to the list. There are also other subclasses that may or may not want callbacks.

class Xyz extends Abc {
  String _data = '';

  Xyz() {
    this.addedValue = _added;
  }

  void _added(String s) {
    _data += '$s,';
    print('data: $_data');
  }
}
main() {
  var a = Xyz();
  a.add('hello');
  a.add('goodbye');

  a.addedValue('a'); // Prefer if this was not possible.
  a.addedValue = null; // Prefer if this was not possible.
}

What is the cleanest way to provide the superclass with the callback method?

  • I can't pass it in a constructor because it is an instance method.
  • I can't make it static because it needs access to the instance.
  • I would prefer not to expose the callback methods/setters beyond the subclass.
  • The superclass and subclass will not be in the same library.
pauleohare
  • 209
  • 2
  • 8
  • Does it need to be a callback? The base class could have `void addedValue(String s) {}` and let derived classes override it as they wish. (Or have the derived classes override `add` itself and do extra work after calling `super.add(s)`.) Alternatively, you could use [`package:meta`](https://pub.dev/packages/meta)'s [`@protected` annotation](https://pub.dev/documentation/meta/latest/meta/protected-constant.html), but note that it will produce only analysis warnings, not prevent the code from executing. – jamesdlin Apr 17 '20 at 10:07
  • Overriding `void addedValue(String s) {}` is fine but still exposes the method. Overriding `add` is not enough because the superclass could modify the list in other ways at a later date so a callback is safer. Thanks. – pauleohare Apr 17 '20 at 10:39
  • Also, I'm not a fan of `@protected`, I prefer either you can or you can't. – pauleohare Apr 17 '20 at 10:54
  • Found this on hiding methods [The "hidden forwarders" pattern in Dart](https://gist.github.com/munificent/e894b14d3e5a26352f30af4b960e0a96) – pauleohare Apr 17 '20 at 12:44

2 Answers2

1

You could make a method like setCallback in the Abc abstract class which makes it possible to set the callback but no longer allow it to be called. This will allow us to set the callback in the constructor for Xyz:

abstract class Abc {
  final _list = <String>[];
  Function(String) _addedValue;

  void add(String s) {
    _list.add(s);
    if (this._addedValue != null) this._addedValue(s);
  }

  void setCallback(Function(String) callback) => _addedValue = callback;
}

class Xyz extends Abc {
  String _data = '';

  Xyz() {
    setCallback(_added);
  }

  void _added(String s) {
    _data += '$s,';
    print('data: $_data');
  }
}

main() {
  var a = Xyz();
  a.add('hello');
  a.add('goodbye');

  a.addedValue('a'); // No longer possible
  a.addedValue = null; // No longer possible
}

I did also make the following solution but it is so ugly and totally stupid that I needed to make another more sensible solution to present first... But I will say it "works" if the intention are also to confuse other people.

The concept of the following implementation is you instead of saving a variable to the callback, you are saving a variable to a method which can give the callback method.

abstract class Abc {
  final _list = <String>[];
  Function(String) Function(Abc) _getCallback;

  Abc(this._getCallback);

  void add(String s) {
    _list.add(s);
    if (this._getCallback != null && this._getCallback(this) != null)
      this._getCallback(this)(s);
  }
}

class Xyz extends Abc {
  String _data = '';

  Xyz() : super((obj) => obj is Xyz ? obj._added : null);

  void _added(String s) {
    _data += '$s,';
    print('data: $_data');
  }
}

main() {
  var a = Xyz();
  a.add('hello');
  a.add('goodbye');

  a.addedValue('a'); // No longer possible
  a.addedValue = null; // No longer possible
}
julemand101
  • 28,470
  • 5
  • 52
  • 48
  • The first solution is pretty clean and I can only allow it to be set once or override it in the subclass to do nothing or throw an error. The second solution is clever but nasty. – pauleohare Apr 17 '20 at 12:11
0

This was something I came up.

abstract class Abc {
  final _list = <String>[];
  //final Function(Abc, String) _addedValue;
  final dynamic _addedValue;

  Abc(this._addedValue);

  void add(String s) {
    _list.add(s);
    if (this._addedValue != null) this._addedValue(this, s);
  }
}

class Xyz extends Abc {
  String _data = '';

  Xyz() : super(Xyz._added);

  static void _added(Xyz instance, String s) {
    instance._data += '$s,';
    print('data: ${instance._data}');
  }
}

main() {
  var a = Xyz();
  a.add('hello');
  a.add('goodbye');
}

I'd like to be able to type _addedValue correctly in Abc but dynamic was the only way I could get it to work. Tried covariant but that's not allowed in this case.

pauleohare
  • 209
  • 2
  • 8