mjordan
mjordan

Reputation: 769

dart advantage of a factory constructor identifier

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?

Upvotes: 74

Views: 45463

Answers (4)

jamesdlin
jamesdlin

Reputation: 90174

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

Upvotes: 54

Saravanan
Saravanan

Reputation: 911

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.

Upvotes: -1

Alessio
Alessio

Reputation: 3193

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.

Upvotes: 4

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 658165

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.

Upvotes: 62

Related Questions