Saltar al contenido principal

Clases

Resumen de clases, instancias de clases y sus miembros.

Dart es un lenguaje orientado a objetos con clases y herencia basada en mixins. Cada objeto es una instancia de una clase, y todas las clases excepto Null descienden de Object. La herencia basada en mixins significa que aunque cada clase (excepto la clase superior, Object?) tiene exactamente una superclase, el cuerpo de una clase se puede reutilizar en múltiples jerarquías de clases. Los métodos de extensión son una forma de añadir funcionalidad a una clase sin cambiar la clase ni crear una subclase. Los modificadores de clase te permiten controlar cómo las bibliotecas pueden crear subtipos de una clase.

Usar miembros de clase

#

Los objetos tienen miembros que consisten en funciones y datos (métodos y variables de instancia, respectivamente). Cuando llamas a un método, lo invocas en un objeto: el método tiene acceso a las funciones y datos de ese objeto.

Usa un punto (.) para hacer referencia a una variable de instancia o método:

dart
var p = Point(2, 2);

// Get the value of y.
assert(p.y == 2);

// Invoke distanceTo() on p.
double distance = p.distanceTo(Point(4, 4));

Usa ?. en lugar de . para evitar una excepción cuando el operando del extremo izquierdo es null:

dart
// If p is non-null, set a variable equal to its y value.
var a = p?.y;

Usar constructores

#

Puedes crear un objeto usando un constructor. Los nombres de los constructores pueden ser ClassName o ClassName.identifier. Por ejemplo, el siguiente código crea objetos Point usando los constructores Point() y Point.fromJson():

dart
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

El siguiente código tiene el mismo efecto, pero usa la palabra clave opcional new antes del nombre del constructor:

dart
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

Algunas clases proporcionan constructores constantes. Para crear una constante en tiempo de compilación usando un constructor constante, coloca la palabra clave const antes del nombre del constructor:

dart
var p = const ImmutablePoint(2, 2);

Construir dos constantes idénticas en tiempo de compilación da como resultado una única instancia canónica:

dart
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // They are the same instance!

Dentro de un contexto constante, puedes omitir el const antes de un constructor o literal. Por ejemplo, mira este código, que crea un mapa const:

dart
// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

Puedes omitir todos excepto el primer uso de la palabra clave const:

dart
// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

Si un constructor constante está fuera de un contexto constante y es invocado sin const, crea un objeto no constante:

dart
var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant

assert(!identical(a, b)); // NOT the same instance!

Obtener el tipo de un objeto

#

Para obtener el tipo de un objeto en tiempo de ejecución, puedes usar la propiedad runtimeType de Object, que devuelve un objeto Type.

dart
print('The type of a is ${a.runtimeType}');

Hasta aquí, has visto cómo usar clases. El resto de esta sección muestra cómo implementar clases.

Variables de instancia

#

Así es como declaras variables de instancia:

dart
class Point {
  double? x; // Declare instance variable x, initially null.
  double? y; // Declare y, initially null.
  double z = 0; // Declare z, initially 0.
}

Una variable de instancia no inicializada declarada con un tipo anulable tiene el valor null. Las variables de instancia no anulables deben inicializarse al declararse.

Todas las variables de instancia generan un método getter implícito. Las variables de instancia no finales y las variables de instancia late final sin inicializadores también generan un método setter implícito. Para más detalles, consulta Getters y setters.

dart
class Point {
  double? x; // Declare instance variable x, initially null.
  double? y; // Declare y, initially null.
}

void main() {
  var point = Point();
  point.x = 4; // Use the setter method for x.
  assert(point.x == 4); // Use the getter method for x.
  assert(point.y == null); // Values default to null.
}

Inicializar una variable de instancia que no sea late donde se declara establece el valor cuando se crea la instancia, antes de que se ejecuten el constructor y su lista de inicializadores. Como resultado, la expresión de inicialización (después del =) de una variable de instancia que no sea late no puede acceder a this.

dart
double initialX = 1.5;

class Point {
  // OK, can access declarations that do not depend on `this`:
  double? x = initialX;

  // ERROR, can't access `this` in non-`late` initializer:
  double? y = this.x;

  // OK, can access `this` in `late` initializer:
  late double? z = this.x;

  // OK, `this.x` and `this.y` are parameter declarations, not expressions:
  Point(this.x, this.y);
}

Las variables de instancia pueden ser final, en cuyo caso deben establecerse exactamente una vez. Inicializa las variables de instancia final que no sean late al declararse, usando un parámetro del constructor, o usando la lista de inicializadores de un constructor:

dart
class ProfileMark {
  final String name;
  final DateTime start = DateTime.now();

  ProfileMark(this.name);
  ProfileMark.unnamed() : name = '';
}

Si necesitas asignar el valor de una variable de instancia final después de que comience el cuerpo del constructor, puedes usar uno de los siguientes métodos:

  • Calcula el valor en un constructor de tipo factory, y luego pásalo a un constructor generativo que inicialice la variable de instancia final.
  • Usa late final, pero ten cuidado: un late final sin un inicializador añade un setter a la API.

Interfaces implícitas

#

Cada clase define implícitamente una interfaz que contiene todos los miembros de instancia de la clase y de cualquier interfaz que implemente. Si quieres crear una clase A que soporte la API de la clase B sin heredar la implementación de B, la clase A debe implementar la interfaz B.

Una clase implementa una o más interfaces declarándolas en una cláusula implements y luego proporcionando las APIs requeridas por las interfaces. Por ejemplo:

dart
// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final String _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

  // In the interface.
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// An implementation of the Person interface.
class Impostor implements Person {
  String get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

Aquí tienes un ejemplo de cómo especificar que una clase implementa múltiples interfaces:

dart
class Point implements Comparable, Location {
  ...
}

Variables y métodos de clase

#

Usa la palabra clave static para implementar variables y métodos a nivel de clase.

Variables estáticas

#

Las variables estáticas (variables de clase) son útiles para constantes y estados a nivel de clase:

dart
class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

Las variables estáticas no se inicializan hasta que se utilizan.

Métodos estáticos

#

Los métodos estáticos (métodos de clase) no operan sobre una instancia y, por lo tanto, no tienen acceso a this. Sin embargo, sí tienen acceso a variables estáticas. Como muestra el siguiente ejemplo, los métodos estáticos se invocan directamente sobre una clase:

dart
import 'dart:math';

class Point {
  double x, y;
  Point(this.x, this.y);

  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}

Puedes usar métodos estáticos como constantes en tiempo de compilación. Por ejemplo, puedes pasar un método estático como parámetro a un constructor constante.