3

I have an abstract superclass with a factory that returns an instance of a subclass. Is it possible to have a method that is implemented only in superclass? In the following code, for instance, would it be possible to remove Wind::act()?

abstract class Element {
  final String action;    // what it does
  String act() => action; // do it

  factory Element() {
    return new Wind();
  }
}

class Wind implements Element {
  final action = "blows";
  act() => action;  // Why is this necessary?
}

void main() {
  print(new  Element().act());
}

When removing Wind::act(), there is an error about it missing. Also, when extending rather than implementing the superclass, leaving out the subclass implementation doesn't cause an error. But with a factory method, extending is not an option.

Tom Russell
  • 1,015
  • 2
  • 10
  • 29
  • I guess what I'm asking is whether there is a way, when using a factory in a superclass, to avoid having to implement every method in every subclass. This would be bad, methinks. – Tom Russell Nov 12 '15 at 07:19

1 Answers1

4

To inherit functionality from Element in Wind, you need to either extend or mix-in Element in Wind. Merely implementing an interface will not inherit any implementation.

So, you need to have class Wind extends Element { ... }. That's not currently possible because Element has no generative constructor that Wind can use as super-constructor. So, you need to add that too, and make sure to initialize the action field in that constructor.

class Element {
  final String action;
  Element._(this.action);    // Generative constructor that Wind can use.
  factory Element() = Wind;  // Factory constructor creating a Wind.
  String act() => action;
}
class Wind extends Element {
  Wind() : super._("blows");
}

The generative constructor doesn't need to be private, but if you are declaring and using all the classes only inside your own library, it might as well be.

Another option is to have a separate ElementBase class containing the action field and act function and an empty-named generative constructor. Mixins are not a good choice in this case, because there is no good way to make action final when mixins can't have constructors.

abstract class Element {
  String get action;
  factory Element() = Wind;
  String act();
}
class ElementBase implements Element {
  final String action;
  ElementBase(this.action);
  String act() => action;
}
class Wind extends ElementBase {
  Wind() : super("blow");
}

It's a common problem to want both a generative constructor for subclasses and a factory constructor generating the default implementation in an interface/skeleton class. The List and Map interfaces have this problem, and have solved it by exposing ListBase and MapBase. I think that is the best solution when you are exposing the superclass to other users in other libraries. If it's only used internally by yourself, I'll use the private/non-default-named generative constructor in the superclass.

lrn
  • 64,680
  • 7
  • 105
  • 121
  • Thanks for the very nice answer to the question I asked but also some other things I've been wondering about, like how the Dart team exports these interfaces. I think I prefer the 2nd version as the interface can be tucked away, and can be trimmed down to only a factory statement. Maybe it could be referred to as simply an abstract factory? Or maybe a factory proxy? BTW, that's kind of an odd assignment statement with "Element()" on the left. Any remarks? Also, when using the longer form ("return new Wind()") you can then parameterize both Element() and Wind(). – Tom Russell Nov 13 '15 at 07:06
  • Parameterizing the factory of course allows you to both determine the subclass that gets instantiated and also pass parameters to its constructor. Very kewl :-) – Tom Russell Nov 13 '15 at 07:12
  • With 1k views, people might enjoy seeing some interaction with this interface. I'm too out of practice to easily provide it at this time. – Tom Russell Aug 02 '21 at 09:10