Dart Constructors 101
👉🏽 Part 2: Factory Constructors
A beginner-friendly tutorial for Dart constructors
For Part 1: Generative Constructors
Why do we need another type of constructor?
A generative constructor always creates a new instance, however in some cases we would like to return an existing instance of a class/subclass, or even return nothing.
Let’s compare named generative vs. factory:
class User {
String name;
int id;// Named generative constructor
User.fromJson(Map<String,Object> json)
: name = json['name'] as String,
id = json['id'] as int;// Factory constructor
factory User.fromJson(Map<String,Object> json) {
final id = json['id'] as int;
final name = json['name'] as String;
return User(id:id, name:name);}
Generative:
- always creates the instance itself
- simply assigns values to instance fields in an initializer list, with no extra business logic. (actually, we can call a static method here, but I am not sure if it is a best practice)
Factory:
- calls the default constructor to create the instance and just returns it
- we can handle any business logic in the body of the factory
- we can throw an error instead of returning a new instance ( for example; if
id
is not a valid positive integer )
Now, let’s see some common use cases of factory constructors.
Singletons
Here we have 2 constructors; a private named (._internal) and a factory constructor. When the factory is called, it checks whether an instance already exists. If null, we are calling the internal constructor to create a new instance and assign it to the_instance.
Click here to run this code snippet above to see the creation dates of all three instances 🧐
Return a cached instance
Let's say we need to fetch a certain object from a database of cached objects, and if the needed object is not available we would like to create one and add it to our database. Thus, next time we need it, we will get it from the database again.
For example, we need brushes with different colors in our app.
Here we have 2 constructors; a private default constructor, and a factory. Whenever a brush with the desired color is demanded, the factory checks whether it is already in _cache
box, if exists returns it, or else creates a new one, put it in the _cache
and then returns this new instance. (Have a look at the putIfAbsent method, which is really versatile) Click to run this code on DartPad
Create a subclass instance
Think about the following class — subclass hierarchy.
We want to create any of the current and future subclass instances depending on some business logic. That means, the client code (the code that needs an instance) does not know the creation logic and just asks for the appropriate instance as:
Shape newShape = Shape(); //may also supply some arguments
For instance:
abstract class Shape {
factory Shape(String type) {
if (type == 'circle') return Circle(); // return circle instance
if (type == 'square') return Square(); // return square instance
throw "Can't create $type."; // return nothing but err
}
}
Shape circle = Shape('circle'); // a circle instance
Shape square = Shape('square'); // a square instance
In such a case you cannot use a generative constructor and should use a factory constructor in the parent class. And as you see the factory gives the option to throw an error after some decision-making. For the full implementation detail checkout this official documentation here.
There exists some other detail regarding constructors, for example: forwarding, implementing assertions, etc. However, my concern here is to give a beginner-friendly tutorial for whoever is confused about all these :)
Let me know if something is missing or needs to be updated. 😎