{ T get value; set value(T newValue); } I'm trying to o" /> { T get value; set value(T newValue); } I'm trying to o" /> { T get value; set value(T newValue); } I'm trying to o"/>

how to override a setter of an abstract class to make the value final

33 Views Asked by At

Given an abstract class "Foo" that has a getter and setter for a "value"

abstract class Foo<T> {
  T get value;
  set value(T newValue);
}

I'm trying to override getter and setter to make the value final;
with the implementation below

class Bar implements Foo<String> {
  @override
  final String value;
  const Bar(this.value);
}

the linter complains Missing concrete implementation of 'setter Foo.value'.,
but if we add the implementation

class Bar implements Foo<String> {
  @override
  final String value;
  const Bar(this.value);
  @override
  set value(String newValue) => value = newValue;
}

when we use the setter

void main() {
  const bar = Bar('hello');
  bar.value = 'world';
  print(bar.value);
}

we don't receive ant warning about using the setter for a final value, but at runtime we incur in this error

Uncaught RangeError: Maximum call stack size exceededError: RangeError: Maximum call stack size exceeded

the problem seems to be only for the setter, therefor\

what is correct way to override a setter of an abstract class to make the value final?

1

There are 1 best solutions below

0
jamesdlin On BEST ANSWER

There is no correct way to disable a setter in the base class because doing so is fundamentally incorrect. A derived class must be substitutable for the base class. If the derived class did not provide a setter, then it would no longer be providing the base class's interface and would violate the substitution principle. Consider:

void updateFoo<T>(Foo<T> foo, T newValue) => foo.value = newValue;

void main() {
  var bar = Bar('hello');
  updateFoo(bar, 'world');
}

It is not possible to issue any compile-time warnings or errors; the implementation and call of updateFoo are both legal from static analysis.

Instead consider:

  • Inverting the class hierarchy if possible: make read-only classes the base classes and provide setters or mutators in the derived classes.
  • Accept a setter that either does nothing (although callers likely would not expect that) or that throws an exception at runtime (this is what things such as UnmodifiableListView do).

Also, your specific exception occurs because of a different mistake in your implementation. When you do:

  @override
  set value(String newValue) => value = newValue;

then the value = newValue; statement will trigger the value setter again, resulting in infinite recursion as hinted at by the error message. If you instead tried to do:

  final String _value;

  @override
  String get value => _value;

  @override
  set value(String newValue) => _value = newValue;

then your Bar implementation would have generated a compile-time error for trying to reassign _value, which is final. And if you made _value non-final, then the compiler would have generated a compile-time error telling you that Bar then could not have a const constructor.