Dart Clases y Objetos
Las clases son el concepto central de la programación orientada a objetos; son una descripción abstracta de las cosas del mundo real.
Los objetos son instancias concretas de una clase. Este capítulo presenta la definición de clases en Dart, los constructores, las variables miembro y métodos, getters/setters y los miembros estáticos.
Definición de clases e instanciación
Una clase es la plantilla para crear objetos; define qué atributos (datos) y comportamientos (métodos) tiene un objeto.
Ejemplo
class User {
// Variables miembro (atributos)
String name = '';
int age = 0;
// Métodos miembro (comportamientos)
void introduce() {
print('Hola, soy $name y tengo $age años.');
}
bool isAdult() {
return age >= 18;
}
}
void main() {
// Instanciación: con la palabra clave new (en Dart 2.0+ se puede omitir new)
var user1 = User();
user1.name = 'ejemplo';
user1.age = 10;
// Acceder a variables miembro y métodos
print('Usuario: ${user1.name}');
user1.introduce();
print('¿Es adulto?: ${user1.isAdult()}');
// Crear otro objeto, independientes entre sí
var user2 = User();
user2.name = 'Xiaoming';
user2.age = 20;
user2.introduce();
}
用户名: ejemplo 你好,我是 ejemplo,今年 10 岁。 是否成年: false 你好,我是 小明,今年 20 岁。
Desde Dart 2.0, la palabra clave new es opcional al instanciar. La convención de la comunidad es omitir new para mantener el código limpio. Los ejemplos siguientes de este tutorial omitirán new.
Constructor
Un constructor es un método especial que se invoca automáticamente al crear un objeto, para inicializar su estado.
Dart ofrece múltiples formas de constructores, es muy flexible.
Constructor predeterminado
Si no defines un constructor, Dart proporciona automáticamente un constructor predeterminado sin parámetros.
Constructor ordinario
Ejemplo
String name;
int age;
// Constructor ordinario: tiene el mismo nombre que la clase
Student(String name, int age) {
this.name = name;
this.age = age;
}
void printInfo() {
print('Estudiante: $name, edad: $age');
}
}
void main() {
var s1 = Student('ejemplo', 10);
var s2 = Student('Xiaoming', 15);
s1.printInfo();
s2.printInfo();
}
学生: ejemplo, 年龄: 10 学生: 小明, 年龄: 15
Constructor de azúcar sintáctico
Cuando los parámetros del constructor se asignan directamente a variables miembro, Dart ofrece una sintaxis abreviada.
Ejemplo
String name;
int age;
// Azúcar sintáctico: this.nombreDelParámetro asigna automáticamente el parámetro a la variable miembro del mismo nombre
// Esta forma es más concisa que escribir manualmente this.name = name
Student(this.name, this.age);
void printInfo() {
print('Estudiante EJEMPLO: $name, edad: $age');
}
}
void main() {
var s = Student('ejemplo', 10);
s.printInfo();
}
EJEMPLO 学生: ejemplo, 年龄: 10
Constructor nombrado
Una clase puede tener varios constructores nombrados, para diferentes lógicas de inicialización.
Ejemplo
double x;
double y;
// Constructor predeterminado
Point(this.x, this.y);
// Constructor nombrado: crea el origen
Point.origin()
: x = 0,
y = 0;
// Constructor nombrado: crea a partir de un solo valor (x e y son iguales)
Point.diagonal(double value)
: x = value,
y = value;
@override
String toString() => 'Point($x, $y)';
}
void main() {
var p1 = Point(3, 4);
var p2 = Point.origin();
var p3 = Point.diagonal(5);
print('Coordenadas EJEMPLO: $p1, $p2, $p3');
}
EJEMPLO 坐标: Point(3.0, 4.0), Point(0.0, 0.0), Point(5.0, 5.0)
Lista de inicialización
La lista de inicialización se ejecuta antes del cuerpo del constructor; se usa para inicializar campos final o validar parámetros.
Ejemplo
final String name; // Los campos final deben inicializarse en el constructor
final int birthYear;
final int age;
// Lista de inicialización: después de los dos puntos, antes del cuerpo de la función
// La lista de inicialización se ejecuta antes del cuerpo del constructor
Person(this.name, this.birthYear)
: age = 2026 - birthYear, // Propiedad calculada
assert(birthYear > 1900, 'El año de nacimiento no es válido'); // Validación con assert
void printInfo() {
print('Usuario EJEMPLO: $name, nacimiento: $birthYear, edad: $age');
}
}
void main() {
var p = Person('ejemplo', 2016);
p.printInfo();
// La siguiente línea hará fallar el assert (en modo de desarrollo)
// var p2 = Person('error', 1800);
}
EJEMPLO 用户: ejemplo, 出生: 2016, 年龄: 10
assert solo está activo en modo de desarrollo (debug mode) y se ignora en el entorno de producción (release mode). Se utiliza para detectar errores lógicos lo antes posible durante el desarrollo.
Variables miembro y métodos
Las variables miembro almacenan el estado del objeto; los métodos miembro definen el comportamiento del objeto.
Ejemplo
// Miembro privado: comienza con guion bajo, solo accesible dentro de este archivo
String _accountNumber;
double _balance = 0;
// Miembro público
String ownerName;
BankAccount(this.ownerName, this._accountNumber);
// Método público
void deposit(double amount) {
if (amount > 0) {
_balance += amount;
print('Depósito de ¥$amount, saldo actual: ¥$_balance');
}
}
bool withdraw(double amount) {
if (amount > 0 && amount <= _balance) {
_balance -= amount;
print('Retiro de ¥$amount, saldo actual: ¥$_balance');
return true;
}
print('Saldo insuficiente, retiro fallido');
return false;
}
// Método privado: solo para uso interno de la clase
void _logTransaction(String type, double amount) {
print('[Registro interno] $type: ¥$amount');
}
}
void main() {
var account = BankAccount('EJEMPLO', '6222-0000-1234');
account.deposit(1000);
account.withdraw(300);
account.withdraw(800); // Saldo insuficiente
// El exterior no puede acceder a miembros privados
// print(account._balance); // Error: _balance es privado
}
存入 ¥1000,当前余额: ¥1000 取出 ¥300,当前余额: ¥700 余额不足,取款失败
Getter y Setter
Los getters y setters son métodos especiales para operaciones de lectura y escritura de variables miembro.
Te permiten ejecutar lógica adicional al acceder a las propiedades, manteniendo una sintaxis limpia para el acceso a las mismas.
Ejemplo
double _radius; // Variable privada, almacena el radio
Circle(this._radius);
// Getter: obtiene el diámetro (propiedad calculada)
double get diameter => _radius * 2;
// Getter: obtiene el área
double get area => 3.14159 * _radius * _radius;
// Getter y Setter combinados: exponen la propiedad radius hacia el exterior
double get radius => _radius;
set radius(double value) {
// En el Setter se puede añadir lógica de validación
if (value <= 0) {
throw ArgumentError('El radio debe ser mayor que 0');
}
_radius = value;
}
// Getter de solo lectura: no tiene Setter correspondiente
String get info => 'Círculo(radio: $_radius)';
}
void main() {
var circle = Circle(5);
// Usar el Getter (como acceder a una propiedad normal)
print('Círculo EJEMPLO: ${circle.info}');
print('Diámetro: ${circle.diameter}');
print('Área: ${circle.area.toStringAsFixed(2)}');
// Usar el Setter
circle.radius = 10;
print('Diámetro después del cambio: ${circle.diameter}');
// Intentar establecer un valor no válido lanza una excepción
// circle.radius = -1; // Lanza ArgumentError
}
EJEMPLO 圆形: 圆(半径: 5.0) 直径: 10.0 面积: 78.54 修改后直径: 20.0
La ventaja de los getters y setters es que quien llama no necesita saber si detrás de radius hay un campo simple o una propiedad calculada. Si en el futuro necesitas añadir lógica de validación a radius, solo tienes que cambiar el campo por getter/setter, y el código que lo llama no necesita modificarse. Esta es la ventaja del "encapsulamiento".
Miembros estáticos
Los miembros estáticos pertenecen a la clase en sí, no a una instancia de la clase.
Todas las instancias comparten la misma variable estática; los métodos estáticos se pueden invocar sin crear un objeto.
Ejemplo
// Variable estática: compartida por todas las instancias
static int totalCount = 0;
// Variable de instancia: independiente en cada instancia
String label;
Counter(this.label) {
// Cada vez que se crea una instancia, el contador estático se incrementa en 1
totalCount++;
}
// Método estático: se invoca mediante el nombre de la clase
static void printTotal() {
print('Número total de instancias EJEMPLO: $totalCount');
}
// Los métodos estáticos se usan a menudo para funciones de utilidad
static bool isValidLabel(String label) {
return label.isNotEmpty && label.length <= 20;
}
}
void main() {
// Método estático: se invoca directamente mediante el nombre de la clase, sin necesidad de crear una instancia
print('¿Es válida la etiqueta?: ${Counter.isValidLabel('test')}');
var c1 = Counter('Contador A');
Counter.printTotal(); // 1
var c2 = Counter('Contador B');
var c3 = Counter('Contador C');
Counter.printTotal(); // 3
// Variable estática: se accede mediante el nombre de la clase
print('Conteo final: ${Counter.totalCount}');
}
标签是否有效: true EJEMPLO 总实例数: 1 EJEMPLO 总实例数: 3 最终计数: 3
Otras extensionesLos métodos estáticos no pueden acceder a miembros de instancia (porque no hay this). Los métodos estáticos son adecuados para funciones de utilidad y lógica de fábrica, pero no debes abusar de ellos: los métodos estáticos no pueden ser sobrescritos por subclases, y su uso excesivo reduce la testabilidad y flexibilidad del código.