7

I've recently run into some examples that make use of abstract classes as interfaces but also add factory constructors to the abstract interface so it can in a sense be "newed" up. For example:

abstract class WidgetService {
    factory WidgetService() = ConcreteWidgetService;

    Widget getWidget();
    void saveWidget(Widget widget);
}

class ConcreteWidgetService extends BaseWidgetService implements WidgetService {

    WidgetService();

    Widget getWidget() {
        // code to get widget here
    }

    void saveWidget(Widget widget) {
        // code to save widget here
    }
}

Usages of this service would be in some other service or component like so:

WidgetService _service = new WidgetService();

Based on my understanding of this sample, the line above would essentially "new" up a WidgetService, which usually produces an warning from the Dart analyzer, and said service variable would actually be an instance of ConcreateWidgetService based on the assignment of the ConcreateWidgetService to the factory constructor of the WidgetService.

Is there a benefit to this approach? From my OOP experience, I've used abstract classes/interfaces to program against when I didn't know the concrete type I would be given. Here it seems we are assigning the concrete type immediately to the abstract factory constructor. I guess my second question would be in this instance why not just use the ConcreteWidgetService directly here instead of repeating all the method signatures?

Darryl Faint
  • 125
  • 1
  • 1
  • 6

2 Answers2

16

In your example the benefits are limited but in more complex situations the benefits become more clear.

A factory constructor allows you more control about what the constructor returns. It can return an instance of a subclass or an already existing (cached) instance.

It can return different concrete implementations based on a constructor parameter:

abstract class WidgetService {
  WidgetService _cached;

  factory WidgetService(String type) {
    switch (type) {
      case 'a':
        return ConcreteWidgetServiceA();
      case 'b':
        return ConcreteWidgetServiceB();
      default:
        return _cached ??= DummyWidgetServiceA();
    }
  }

  Widget getWidget();

  void saveWidget(Widget widget);
}

Your example seems to be a preparation to be extended eventually to such a more flexible approach.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    I assume you meant ConcreteWidgetServiceB() in case 'b': – Alan Mark Kristensen Jul 21 '20 at 12:29
  • 1
    @AlanMarkKristensen typical copy-paste error. Thanks! - fixed – Günter Zöchbauer Jul 21 '20 at 12:44
  • @GünterZöchbauer How do you implement the exact same thing you have above, with null safety? – stackrocha Nov 24 '21 at 09:19
  • 1
    @stackrocha `WidgetService? _cached;` If this does not solve your problem please create a new question with a full explanation what you try to accomplish and where you failed. – Günter Zöchbauer Nov 24 '21 at 09:29
  • Doesn't this cause a circular dependency? The subclasses would have to import the abstract class so they can implement the class and the abstract class would have to import the subclasses to create new instances of them? – DecafCoder Sep 09 '22 at 22:01
  • 1
    @DecafCoder You can have them in the same file, but either way, that's not a problem for Dart – Günter Zöchbauer Sep 10 '22 at 10:35
  • @GünterZöchbauer Cheers. I read up on circular deps in Dart and it seems like it doesn't care. I guess it's always been best practice for me to avoid them so my implementation of the above as the abstract class file, the subclass files and a factory file to avoid the circular dependencies. – DecafCoder Sep 10 '22 at 21:17
3

In Dart, all classes are also automatically interfaces. There's nothing strange about creating a instance of - 'newing up' - an 'interface', because it's actually just creating an instance of a class.

The purpose of some abstract classes is specifically to define an interface, but they have factory constructors that return a 'default implementation' of themselves.

For instance:

void main() {
  var v = new Map();
  print(v.runtimeType);
}

prints: _InternalLinkedHashMap - the (current) default implementation of Map.

Core library users can create and using instances of Map without knowing or caring about the implementation they actually get. The library authors may change the implementation without breaking anyone's code.

Of course, other classes may implement Map also.

With respect to your code sample, WidgetService _service = new WidgetService(); does not produce an analyzer warning. See this example on DartPad (Note I fixed a couple of errors in your sample).

I was initially confused by:

factory WidgetService() = ConcreteWidgetService;

instead of:

factory WidgetService() => new ConcreteWidgetService();

but it seems to work.

Argenti Apparatus
  • 3,857
  • 2
  • 25
  • 35