Dart Factory Constructors
A factory constructor decides what object a call returns. It can hand back an existing instance instead of always building a new one.
A factory that caches instances
The factory checks a top-level map first. A name already in the map returns the stored logger.
final Map<String, Logger> _cache = {};
class Logger {
final String name;
Logger._internal(this.name);
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name]!;
}
final created = Logger._internal(name);
_cache[name] = created;
return created;
}
}
void main() {
final a = Logger('app');
final b = Logger('app');
final c = Logger('db');
print(identical(a, b));
print(identical(a, c));
print(a.name);
print(c.name);
}The cache grows only for new keys
Two requests share one key and one request uses another key. The map ends with two entries, not three.
final Map<String, Logger> _cache = {};
class Logger {
final String name;
Logger._internal(this.name);
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name]!;
}
final created = Logger._internal(name);
_cache[name] = created;
return created;
}
}
void main() {
Logger('app');
Logger('app');
Logger('db');
print(_cache.length);
}A factory returning a subtype
The factory reads the requested kind. It returns a Circle for one kind and a Square for the other.
class Shape {
final String kind;
Shape(this.kind);
factory Shape.fromKind(String kind) {
if (kind == 'circle') {
return Circle();
}
return Square();
}
}
class Circle extends Shape {
Circle() : super('circle');
}
class Square extends Shape {
Square() : super('square');
}
void main() {
final a = Shape.fromKind('circle');
final b = Shape.fromKind('box');
print(a.kind);
print(b.kind);
print(a.runtimeType);
print(b.runtimeType);
}Example
final Map<String, Logger> _cache = {};
class Logger {
final String name;
Logger._internal(this.name);
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name]!;
}
final created = Logger._internal(name);
_cache[name] = created;
return created;
}
void log(String message) {
print('[$name] $message');
}
}
void main() {
final app = Logger('app');
final again = Logger('app');
app.log('started');
again.log('ready');
print(identical(app, again));
}Key points
- A normal constructor always creates a fresh instance.
- A factory constructor can return an instance that already exists.
- A factory constructor can also return an instance of a subtype.
- The word factory goes before the constructor name.
- A private named constructor often builds the real instance behind a factory.
- A top-level variable can hold the shared instances a factory reuses.
In this example, both Logger calls ask for the name app. The first call builds a logger through the private constructor and stores it in the map. The second call finds the stored logger and returns it. Both variables hold the same object, so identical prints true.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท DartPad
Exercise
What can a factory constructor do that a normal constructor cannot?