dart advantage of a factory constructor identifier
DartFlutterDart 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 sincenew
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 returningnull
from a factory constructor. Note that returningnull
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 usingnew
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 aFuture
.) - 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.