factory-robot

Problem

Let’s say we want to implement a factory constructor in an abstract class. For example, given the abstract class Position, we want to enforce that all classes that implement it have two capabilities: Position.fromJson and toJson. Notice that the first of these is usually implemented as a factory constructor.

We can show this in the example code below:

abstract class Position {
  factory Position.fromJson(Map<String, dynamic> json);

  Map<String, dynamic> toJson();
}

Unfortunately, the above will throw either one of the following errors:

A function body must be provided. Try adding a function body.dart(missing_function_body)

or

Error: Expected a function body or ‘=>’. Try adding {}.

Why?

This happens because constructors are not part of an interface. This is mentioned directly in the Dart specification which specifies:

“Inheritance of static methods has little utility in Dart. Static methods cannot be overridden.”

So what can we do about it?

Solution

Given the limitations presented above, we need an alternative pattern. This pattern should allow us to meet the principle intent of a factory constructor, which is to allow a class to defer instantiation to a subclass. This alternative pattern should also allow us to take advantage of Dart’s elegantly designed (okay, I’m biased) built in type-safety checks.

We can find this alternative pattern in the Abstract Factory Pattern.

This pattern provides a way to define a factory that can return an abstract class. This allows us to define different factories that can return specific classes, each of which must implement or inherit from the original abstract class.

The following example should make things clearer:

abstract class Position {
  Map<String, dynamic> toJson();
}

abstract class PositionFactory {
  Position fromJson(Map<String, dynamic> json);
}

Notice how the factory is now a seperate class on its own.

We can then use this abstract “factory” class to return a more specific implementation:

/// a specific implementation of the abstract class Position
class VectorPosition implements Position {
  final double x;
  final double y;
  VectorPosition(this.x, this.y);

  Map<String, dynamic> toJson() {
    return {
      'x': x,
      'y': y
    };
  }
}

/// a factory that only returns the specific implementation of the abstract class Position
class VectorPositionFactory implements PositionFactory {
  VectorPosition fromJson(Map<String, dynamic> json) {
    return VectorPosition(json['x'], json['y']);
  }
}



void main() {
  Map<String, double> json = {
    'x': 2,
    'y': 3,
  };

  VectorPosition vector = VectorPositionFactory().fromJson(json);
  print(vector.x);
}
> 2.0

Conclusion

The above abstract factory pattern approach allows us to utilise the factory pattern in our code, while allowing us to keep and enforce type-checking on classes that implement code that works with the abstract Position class.