dart advantage of a factory constructor identifier

DartFlutter

Dart Problem Overview


I've been investigating JSON parsing for my Flutter app and have a question about factory constructors that I can't resolve. I'm trying to understand the advantage of using a factory constructor versus a plain constructor. For example, I see quite a few JSON parsing examples that create a model class with a JSON constructor like this:

class Student{
  String studentId;
  String studentName;
  int studentScores;

  Student({
    this.studentId,
    this.studentName,
    this.studentScores
  });

  factory Student.fromJson(Map<String, dynamic> parsedJson){
    return Student(
      studentId: parsedJson['id'],
      studentName : parsedJson['name'],
      studentScores : parsedJson ['score']
    );
  }
}

I've also seen an equal number of examples that DON'T declare the constructor as a factory. Both types of classname.fromJSON constructors create an object from the JSON data so is there an advantage to declaring the constructor as a factory or is using a factory here superfluous?

Dart Solutions


Solution 1 - Dart

A normal constructor always returns a new instance of the current class (except when the constructor throws an exception).

A factory constructor is quite similar to a static method with the differences that it

  • can only return an instance of the current class or one of its subclasses
  • can be invoked with new but that is now less relevant since new became optional.
  • has no initializer list (no : super())

So a factory constructor can be used

  • to create instances of subclasses (for example depending on the passed parameter
  • to return a cached instance instead of a new one
  • to prepare calculated values to forward them as parameters to a normal constructor so that final fields can be initialized with them. This is often used to work around limitations of what can be done in an initializer list of a normal constructor (like error handling).

In your example this code

  studentId: parsedJson['id'],
  studentName : parsedJson['name'],
  studentScores : parsedJson ['score']

could be moved to the body of a normal constructor because no final fields need to be initialized.

Solution 2 - Dart

In the particular example in the question, there's no advantage to using a factory constructor. It makes no difference to callers (there is no expectation to receive an already-existing object), and this particular factory constructor could have been a normal constructor that delegated to the main constructor instead.

In general, the factory keyword is not very useful and provides an advantage only in special circumstances.


A factory constructor vs. a normal constructor

  • A factory constructor invokes another constructor.
  • Since a factory constructor does not directly create a new instance, it cannot use a constructor initializer list.
  • A normal constructor always returns a new instance of the class. A factory constructor is permitted to return an existing instance, an instance of a derived class, or null. (However, some people dislike returning null from a factory constructor. Note that returning null from a factory constructor is disallowed with null-safety.)
  • Due to the above, an extending class cannot invoke a factory constructor as the superclass constructor. A class that provides only factory constructors therefore cannot be extended with derived classes.

A factory constructor vs. a static method

  • A factory constructor can be the unnamed, default constructor of a class.
  • A factory constructor can be used with new. (But using new is now discouraged.)
  • Until Dart 2.15, constructors could not be used as tear-offs (i.e., they could not be used as callbacks), whereas static methods could.
  • Static methods can be async. (A factory constructor must return a type of its class, so it cannot return a Future.)
  • Factory constructors can be declared const.
  • In null-safe Dart, a factory constructor cannot return a nullable type.
  • In generated dartdoc documentation, a factory constructor obviously will be listed in the "Constructors" section (which is prominently at the top) whereas a static method will be in the "Static Methods" section (which currently is buried at the bottom).

Solution 3 - Dart

After I've been noticing and wondering the same, and given I don't think the other answers actually answer the question ("I've been investigating JSON parsing [...] I'm trying to understand the advantage of using a factory constructor verses a plain constructor"), here my try:

there's no advantage or difference that I could see or understand, when parsing json, in using a factory constructor instead of a plain constructor. I tried both, and both works fine, with all the types of parameters. I decided eventually to adopt the factory constructor, because of the convenience of how the code is written, and readability, but it's a matter of choice and both will work fine in all the cases.

Solution 4 - Dart

One of the uses of factory constructor is, we can decide which instance to create, at run-time and move all the logic to the parent's factory constructor

let's say you have 1 parent class and 2 subclasses

class GolderRetriever extends Dog{
   GolderRetriever(String name):super(name);
}
class Labrador extends Dog{
  Labrador(String name):super(name);
}

Then we have the parent class

class Dog{
  final String name;
  Dog(this.name);
  
  
  factory Dog.createInstance({required String name,DogType type=DogType.UNKNOWN}){
    
    if(type==DogType.GOLDEN_RETRIEVER){
      return GolderRetriever(name);
    }
    else if(type==DogType.DALMATION){
      return Labrador(name);
    }else{
    return Dog(name);
    }
  }
}

and also I have enum DogType

enum DogType{
  GOLDEN_RETRIEVER,DALMATION,UNKNOWN
}

Then in the main Method, you just delegate which subclass instance you want to create to the parent Dog class

main() {
     
  Dog myDog = Dog.createInstance(name:"Rocky",type:DogType.DALMATION);
  Dog myNeighboursDog =  Dog.createInstance(name:"Tommy",type:DogType.GOLDEN_RETRIEVER);
  Dog strayDog = Dog.createInstance(name:"jimmy");
}

you can't do this with a named constructor as you can create only the instance of that class(Dog class), not its subtypes.

Now the responsibility of which instance to create is delegated to the parent class. This can remove a lot of if-else boilerplate code. When you want to change the logic, you just change that in Animal class alone.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmjordanView Question on Stackoverflow
Solution 1 - DartGünter ZöchbauerView Answer on Stackoverflow
Solution 2 - DartjamesdlinView Answer on Stackoverflow
Solution 3 - DartAlessioView Answer on Stackoverflow
Solution 4 - DartSaravananView Answer on Stackoverflow