Os construtores son métodos especiais nunha clase que se utilizan para crear e inicializar obxectos (instancias) desa clase. Cando creas un novo obxecto, o construtor é o primeiro código que se executa nese obxecto.
En Dart, as clases teñen un construtor por defecto sen argumentos (se non defines ningún). Non obstante, na maioría dos casos, quererás definir o teu propio construtor para inicializar as variables da instancia.
🔸 Tipos de Construtores #
Dart ofrece varios tipos de construtores para diferentes escenarios de inicialización e creación de obxectos:
- Construtor por Defecto (Default Constructor)
- Construtor Xenerativo (Generative Constructor)
- Construtor de Redirección (Redirecting Constructor)
- Construtor Constante (Const Constructor)
- Construtor Factory (Factory Constructor)
🔹 Constructor por defecto #
Se non declaras ningún construtor na túa clase, Dart proporciona un construtor público sen argumentos. Este construtor non fai nada máis que crear o obxecto.
class Persoa {
String nome = '';
int idade = 0;
}
void main() {
var p = Persoa(); // Chamamos ao constructor por defecto
p.nome = 'Diego';
p.idade = 22;
}
class Cadro {
String cor = 'negro'; // Valor inicial por defecto
// Dart proporciona un construtor implícito: Cadro() {}
}
void main() {
var meuCadro = Cadro();
print('Cor do cadro: ${meuCadro.cor}'); // Saída: Cor do cadro: negro
}
🔹 Constructor xenerativo (generative constructor) #
Este é o tipo de construtor máis común, usado para crear unha nova instancia da clase. Podes telo con ou sen parámetros.
Podemos definir un constructor personalizado empregando o mesmo nome da clase. Este tipo de constructor permite inicializar os atributos directamente.
🔹 Constructores sen nome #
É o construtor principal dunha clase. Unha clase só pode ter un construtor sen nome.
class Persoa {
String nome;
int idade;
// Construtor sen nome para inicializar 'nome' e 'idade'
Persoa(this.nome, this.idade) {
print('Nova persoa creada: $nome, $idade anos.');
}
void saudar() {
print('Ola, son $nome.');
}
}
void main() {
var p1 = Persoa('Diego', 30); // Usa o construtor sen nome
p1.saudar(); // Saída: Ola, son Diego.
}
🔹 Constructores nomeados #
Unha clase pode ter varios constructores. Para distinguilos, podemos empregar constructores nomeados.
Isto é útil cando tes múltiples maneiras de inicializar un obxecto.
class Persoa {
String nome;
int idade;
Persoa(this.nome, this.idade);
// Constructor nomeado sen argumentos
Persoa.senDatos() : nome = 'Descoñecido', idade = 0;
void saudar() {
print('Ola! Son $nome e teño $idade anos.');
}
}
void main() {
var p1 = Persoa('Antón', 30);
var p2 = Persoa.senDatos();
p1.saudar(); // Ola! Son Antón e teño 30 anos.
p2.saudar(); // Ola! Son Descoñecido e teño 0 anos.
}
class Punto {
double x, y;
// Construtor principal (sen nome)
Punto(this.x, this.y);
// Construtor nomeado: Punto.orixe()
// Inicializa un punto nas coordenadas (0.0, 0.0)
Punto.orixe() : this(0.0, 0.0);
// Construtor nomeado: Punto.desdeJson()
// Inicializa un punto a partir dun mapa JSON
Punto.desdeJson(Map<String, double> json)
: x = json['x']!,
y = json['y']! {
print('Punto creado desde JSON.');
}
}
void main() {
var p1 = Punto(10.0, 20.0); // Usa o construtor principal
print('Punto p1: (${p1.x}, ${p1.y})');
var pOrixe = Punto.orixe(); // Usa o construtor nomeado .orixe()
print('Punto orixe: (${pOrixe.x}, ${pOrixe.y})');
var jsonDatos = {'x': 5.0, 'y': 15.0};
var pJson = Punto.desdeJson(jsonDatos); // Usa o construtor nomeado .desdeJson()
print('Punto desde JSON: (${pJson.x}, ${pJson.y})');
}
🔹 Construtor de Redirección (Redirecting Constructor) #
Un construtor de redirección non crea unha nova instancia, senón que simplemente chama a outro construtor da mesma clase.
Úsase para evitar a duplicación de código cando varios construtores teñen lóxica de inicialización común.
Utiliza a palabra clave
this
.
class Cor {
int r, g, b;
Cor(this.r, this.g, this.b); // Construtor principal
// Construtor nomeado que redirixe ao construtor principal con valores predeterminados
Cor.negro() : this(0, 0, 0);
// Construtor nomeado que redirixe e calcula valores
Cor.gris(int valor) : this(valor, valor, valor);
}
void main() {
var corDefinida = Cor(255, 0, 0); // Vermello
print('Cor definida: R=${corDefinida.r}, G=${corDefinida.g}, B=${corDefinida.b}');
var corNegra = Cor.negro(); // Redirixe a Cor(0, 0, 0)
print('Cor negra: R=${corNegra.r}, G=${corNegra.g}, B=${corNegra.b}');
var corGris = Cor.gris(128); // Redirixe a Cor(128, 128, 128)
print('Cor gris: R=${corGris.r}, G=${corGris.g}, B=${corGris.b}');
}
🔹 Constructores constantes #
Se queres que as instancias da túa clase poidan ser constantes en tempo de compilación, a clase debe ter un construtor const
. Isto significa que todos os atributos da clase tamén deben ser final
. As instancias const
son canónicas (se dous obxectos const
son idénticos, son o mesmo obxecto).
class PuntoInmutable {
final double x;
final double y;
// Construtor const: todos os atributos deben ser final
const PuntoInmutable(this.x, this.y);
}
void main() {
const p1 = PuntoInmutable(10.0, 20.0);
const p2 = PuntoInmutable(10.0, 20.0);
var p3 = const PuntoInmutable(10.0, 20.0); // Pode ser 'var' se se usa 'const' no valor
print('p1: (${p1.x}, ${p1.y})');
print('p2: (${p2.x}, ${p2.y})');
// As instancias const idénticas apuntan ao mesmo obxecto
print('p1 e p2 son o mesmo obxecto? ${identical(p1, p2)}'); // Saída: true
print('p1 e p3 son o mesmo obxecto? ${identical(p1, p3)}'); // Saída: true
// p1.x = 5; // ❌ Erro: Non se pode modificar unha variable final
}
🔹 A maiores… Construtor factory (factory constructor) #
Os construtores factory
non sempre crean unha nova instancia da clase. Poden devolver unha instancia existente dunha caché, unha subclase, ou unha instancia que se constrúe de forma máis complexa. Úsanse cando a lóxica de creación do obxecto non é tan directa como unha simple inicialización.
Un construtor factory defínese coa palabra clave factory
. Non ten acceso a this
.
class CacheUsuario {
static final Map<String, CacheUsuario> _cache = {};
final String id;
final String nome;
// Construtor privado para evitar instanciación directa
CacheUsuario._(this.id, this.nome);
// Construtor factory: crea ou devolve unha instancia existente
factory CacheUsuario.obter(String id, String nome) {
if (_cache.containsKey(id)) {
print('Obtendo usuario desde caché: $id');
return _cache[id]!;
} else {
final novoUsuario = CacheUsuario._(id, nome);
_cache[id] = novoUsuario;
print('Creando novo usuario e gardando en caché: $id');
return novoUsuario;
}
}
}
void main() {
var usuario1 = CacheUsuario.obter('user123', 'Manuel'); // Crea e garda
var usuario2 = CacheUsuario.obter('user456', 'Laura'); // Crea e garda
var usuario3 = CacheUsuario.obter('user123', 'Manuel'); // Obtén desde caché
print('Usuario 1 é igual a Usuario 3? ${identical(usuario1, usuario3)}'); // Saída: true
}
🧠 Inicialización de Variables de Instancia #
Dart ofrece varias formas de inicializar as variables de instancia:
Parámetros do construtor this
#
A forma máis concisa para inicializar variables de instancia cando o parámetro do construtor ten o mesmo nome que a variable.
class Producto {
String nome;
double prezo;
Producto(this.nome, this.prezo); // Inicializa nome e prezo
}
Listas de inicialización #
Permiten asignar valores aos atributos antes de que se execute o corpo do constructor. Útil para atributos final
.
class Punto {
final double x;
final double y;
// Constructor cunha lista de inicialización
Punto(double xValor, double yValor)
: x = xValor,
y = yValor;
@override
String toString() => 'Punto($x, $y)';
}
void main() {
var p = Punto(2.0, 3.5);
print(p); // Output: Punto(2.0, 3.5)
}
Corpo do construtor #
Para lóxica de inicialización máis complexa que non se pode facer na lista de inicialización.
class Temporizador {
int segundos;
Temporizador(int minutos) {
this.segundos = minutos * 60; // Lóxica no corpo
}
}
Valores por defecto nas declaracións #
Para variables non final.
class Configuracion {
bool modoEscuro = false; // Valor por defecto
}
♻️ Herdanza de Construtores #
En Dart, os construtores
- Non se herdan. As subclases deben definir os seus propios construtores e, se a superclase ten un construtor sen nome, a subclase invocarao implicitamente.
- Se a superclase non ten un construtor sen nome (é dicir, só ten construtores nomeados), a subclase debe invocar explicitamente un dos construtores nomeados da superclase usando
super.nomeConstrutor()
.
class Animal {
String nome;
Animal(this.nome); // Construtor sen nome da superclase
Animal.descoñecido() : nome = 'Descoñecido'; // Construtor nomeado da superclase
}
class Can extends Animal {
String raza;
// 1. Construtor que invoca implicitamente o construtor sen nome da superclase
Can(String nome, this.raza) : super(nome);
// 2. Construtor que invoca explicitamente un construtor nomeado da superclase
Can.cruzamento(String nome) : super.descoñecido() {
this.raza = 'Cruzamento';
this.nome = nome; // A propiedade 'nome' xa foi inicializada por super.descoñecido()
}
}
void main() {
var miCan = Can('Max', 'Labrador');
print('Nome do can: ${miCan.nome}, Raza: ${miCan.raza}');
var canCruzamento = Can.cruzamento('Boby');
print('Nome do can: ${canCruzamento.nome}, Raza: ${canCruzamento.raza}');
}