oopdartdart-polymerangular-dart

Benefits of an abstract class with a factory constructor?


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?


Solution

  • 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.