You see factory Port.fromText(...) in code that creates an object from a string. Must every named constructor be a factory? No. A name labels which construction path you called; factory changes the creation contract, allowing that path to return an object that already exists.
What does an ordinary named constructor do?
Even with the same port value, the ordinary constructor creates separate objects. The factory in this example consults a cache and returns the same instance twice.
A generative named constructor such as Port.direct(8080) creates a new Port from its argument. Calling it twice with the same value still produces two distinct objects.
class Port {
final int value;
Port.direct(this.value);
}
void main() {
final first = Port.direct(8080);
final second = Port.direct(8080);
print(identical(first, second)); // false
}identical checks whether two references are to the exact same object, not whether their port numbers match. Having two objects with equal values is not automatically a performance problem; identity matters only when an actual requirement depends on it.
How can a factory return an existing object?
Now parse a port number from text, validate it, and reuse the object for that number. This cache demonstrates the concept rather than a general recommendation:
class Port {
final int value;
static final Map<int, Port> _cache = {};
Port.direct(this.value);
Port._(this.value);
factory Port.fromText(String raw) {
final value = int.tryParse(raw);
if (value == null || value < 1 || value > 65535) {
throw FormatException('invalid port');
}
return _cache.putIfAbsent(value, () => Port._(value));
}
}
void main() {
final one = Port.fromText('8080');
final two = Port.fromText('8080');
print('${one.value} ${identical(one, two)}');
print(identical(Port.direct(8080), Port.direct(8080)));
try {
Port.fromText('99999');
} on FormatException catch (error) {
print(error.message);
}
}8080 true
false
invalid portfromText parses and checks the range. For 8080, it returns an existing cached object or creates one through the private Port._ constructor and stores it. direct creates a new object each time. Invalid 99999 fails before it reaches the cache. The Dart constructor documentation explicitly includes returning a cached instance as a use for factory.
Do you need a factory just to parse text?
No. Parsing and validation can also be organized in a generative constructor's initialization or a separate static function, depending on the work required. Returning an existing Port is the reason this example uses a factory. Do not add factory merely because you like the constructor's name.
When is caching more burden than benefit?
The _cache retains each port number used for the lifetime of the application. Even though valid port numbers are bounded, entries accumulate, and sharing an object with mutable state could let one screen affect another. This immutable one-field example is simple for showing identity; it is not a reason to cache every model object.
When debugging, distinguish “equal values” from “the same instance.” If value equality is all you need, a straightforward new-object constructor is often clearer. Measure a real bottleneck and consider object lifetime and memory cost before choosing reuse.
Key takeaways
An ordinary named generative constructor creates a new object. A factory may return an existing object or another construction result. Parsing itself does not require a factory; decide based on the creation contract and whether sharing an instance is useful.

