Control total sobre la instanciación con factory
Un constructor factory es, en esencia, un constructor que no garantiza la creación de una nueva instancia de la clase en la que se define. A diferencia de un constructor generativo convencional, que siempre reserva memoria para un nuevo objeto y lo inicializa, un factory puede devolver una instancia ya existente (como en un patrón Singleton), una instancia de una subclase (permitiendo polimorfismo desde la creación) o incluso un objeto recuperado de un caché.
La lógica interna es que, aunque se invoca con la sintaxis de un constructor, el compilador lo trata de forma similar a un método estático con una ventaja crucial: tiene la capacidad de devolver un objeto que cumpla con el tipo de la clase original, algo que un método estático estándar no puede hacer sin un casting manual. Debes usarlo cuando necesites desacoplar la lógica de creación de la instancia de la instancia misma, como al implementar el patrón Factory Method para retornar subtipos o para gestionar un pool de objetos. Sin embargo, si intentas acceder a propiedades de la instancia usando this dentro de su cuerpo, el compilador fallará, ya que para cuando el código del factory se ejecute, la instancia que se pretende devolver aún no ha sido asignada al contexto this. Además, un factory no puede ser utilizado en la lista de inicialización de un constructor, pues su función es retornar un objeto, no configurar los campos de uno que se está creando en ese preciso instante.
abstract class Pago {
final double monto;
final String metodo;
// Constructor generativo privado para forzar el uso de la factory
Pago._(this.monto, this.metodo);
// Factory: implementa el patrón Factory Method para retornar subtipos
factory Pago.crear(double monto, String tipo) {
return switch (tipo) {
'tarjeta' => Tarjeta(monto);
'paypal' => PayPal(monto);
_ => throw ArgumentError('Método de pago no soportado'),
};
}
}
class Tarjeta extends Pago {
Tarjeta(double monto) : super(monto, 'Tarjeta de Crédito');
}
class PayPal extends Pago {
PayPal(double monto) : super(monto, 'PayPal');
}
// Implementación de Singleton usando factory
class DatabaseConnection {
static DatabaseConnection? _instance;
final String connectionString;
// Constructor privado para evitar instanciación externa
DatabaseConnection._internal(this.connectionString);
// Factory: devuelve siempre la misma instancia (Singleton)
factory DatabaseConnection.instance() {
// Si la instancia no existe, se crea mediante el constructor interno
_instance ??= DatabaseConnection._internal('postgres://db.production.internal:5432');
return _instance!;
}
}
void main() {
// Uso de factory para polimorfismo: el tipo es Pago, pero el objeto es Tarjeta o PayPal
final miPago = Pago.crear(150.0, 'paypal');
print('Pagando $${miPago.monto} con ${miPago.metodo}');
// Uso de factory para Singleton: db1 y db2 apuntan al mismo espacio de memoria
final db1 = DatabaseConnection.instance();
final db2 = DatabaseConnection.instance();
print('¿Es la misma conexión de DB?: ${identical(db1, db2)}');
}
En el ejemplo anterior, el constructor Pago.crear es el corazón del polimorfismo. Fíjate cómo, aunque declaramos la variable miPago como de tipo Pago, el cuerpo del factory decide en tiempo de ejecución si instanciar una Tarjeta o una PayPal. Esto es algo que un constructor normal de Pago no podría hacer, ya que un constructor generativo está obligado a devolver una instancia de la clase donde se define.
Por otro lado, en DatabaseConnection.instance(), vemos el patrón Singleton. El constructor _internal es privado, lo que impide que alguien haga new DatabaseConnection(). El factory actúa como un guardián: verifica si _instance ya tiene un valor y, si es así, reutiliza esa referencia en lugar de crear una nueva. Esto es extremadamente eficiente en servicios de backend para evitar múltiples conexiones abiertas a una base de datos. El uso de identical(db1, db2) confirma que ambos objetos son exactamente la misma instancia en la memoria de la VM de Dart.
El error frecuente
Un error común es intentar acceder a variables de instancia dentro de un factory, confundiendo su comportamiento con el de un constructor generativo.
class Session {
String? user;
Session._();
factory Session.withUser(String name) {
// ERROR: 'this' no es accesible en un constructor factory
// print(user);
// Lo correcto es crear la instancia primero y luego trabajar con ella
final nuevaSesion = Session._();
nuevaSesion.user = name;
return nuevaSesion;
}
}
Como puedes ver, el error ocurre porque el factory no tiene un objeto this vinculado. La lógica debe ser: crear la instancia (usando un constructor generativo interno), configurar lo necesario y luego devolver esa instancia ya lista.
N° 51