Saltar al contenido principal

Modificadores de clase

Palabras clave modificadoras para declaraciones de clases para controlar el acceso a librerías externas.

Los modificadores de clases controlan cómo se puede usar una clase o mixin, tanto desde dentro de su propia librería como desde fuera de la librería donde se define.

Las palabras clave modificadoras van antes de la declaración de una clase o mixin. Por ejemplo, escribir abstract class define una clase abstracta. El conjunto completo de modificadores que pueden aparecer antes de la declaración de una clase incluye:

  • abstract
  • base
  • final
  • interface
  • sealed
  • mixin

Solo el modificador base puede aparecer antes de la declaración de un mixin. Los modificadores no se aplican a otras declaraciones como enum, typedef, extension o extension type.

Al decidir si usar modificadores de clase, considera los usos previstos de la clase y de qué comportamientos necesita la clase poder depender.

Sin modificador

#

Para permitir el permiso irrestricto para construir o subtipar desde cualquier librería, usa una declaración class o mixin sin modificador. Por defecto, puedes:

abstract

#

Para definir una clase que no requiera una implementación concreta y completa de toda su interfaz, utiliza el modificador abstract.

Las clases abstractas no se pueden construir desde ninguna librería, ya sea la propia o una librería externa. Las clases abstractas a menudo tienen métodos abstractos.

a.dart
dart
abstract class Vehicle {
  void moveForward(int meters);
}
b.dart
dart
import 'a.dart';

// Error: `Vehicle` can't be instantiated because
// it is marked as `abstract`.
Vehicle myVehicle = Vehicle();

// Can be extended.
class Car extends Vehicle {
  int passengers = 4;

  @override
  void moveForward(int meters) {
    // ...
  }
}

// Can be implemented.
class MockVehicle implements Vehicle {
  @override
  void moveForward(int meters) {
    // ...
  }
}

Si deseas que tu clase abstracta parezca instanciable, define un constructor factory.

base

#

Para imponer la herencia de la implementación de una clase o mixin, utiliza el modificador base. Una clase base no permite la implementación fuera de su propia librería. Esto garantiza:

  • El constructor de la clase base se llama cada vez que se crea una instancia de un subtipo de la clase.
  • Todos los miembros privados implementados existen en los subtipos.
  • A new implemented member in a base class does not break subtypes, since all subtypes inherit the new member.
    • Esto es cierto a menos que el subtipo ya declare un miembro con el mismo nombre y una firma incompatible.

Debes marcar cualquier clase que implemente o extienda una clase base como base, final o sealed. Esto evita que las librerías externas rompan las garantías de la clase base.

a.dart
dart
base class Vehicle {
  void moveForward(int meters) {
    // ...
  }
}
b.dart
dart
import 'a.dart';

// Can be constructed.
Vehicle myVehicle = Vehicle();

// Can be extended.
base class Car extends Vehicle {
  int passengers = 4;
  // ...
}

// ERROR: `Vehicle` can't be implemented in a different library because
// it is marked with `base`.
base class MockVehicle implements Vehicle {
  @override
  void moveForward() {
    // ...
  }
}

interface

#

Para definir una interfaz, utiliza el modificador interface. Las librerías fuera de la propia librería definidora de la interfaz pueden implementar la interfaz, pero no extenderla. Esto garantiza:

  • Cuando uno de los métodos de instancia de la clase llama a otro método de instancia sobre this, siempre invocará una implementación conocida del método de la misma librería.
  • Otras librerías no pueden sobrescribir métodos que los propios métodos de la clase interfaz podrían llamar más tarde de formas inesperadas. Esto reduce el problema de la clase base frágil (fragile base class problem).
a.dart
dart
interface class Vehicle {
  void moveForward(int meters) {
    // ...
  }
}
b.dart
dart
import 'a.dart';

// Can be constructed.
Vehicle myVehicle = Vehicle();

// ERROR: `Vehicle` can't be extended in a different library because
// it is marked with `interface`.
class Car extends Vehicle {
  int passengers = 4;
  // ...
}

// Can be implemented.
class MockVehicle implements Vehicle {
  @override
  void moveForward(int meters) {
    // ...
  }
}

abstract interface

#

El uso más común del modificador interface es definir una interfaz pura. Combina los modificadores interface y abstract para obtener una clase de interfaz abstracta (abstract interface class).

Al igual que una clase interface, otras librerías pueden implementar, pero no pueden heredar, una interfaz pura. Al igual que una clase abstract, una interfaz pura puede tener miembros abstractos.

final

#

Para cerrar la jerarquía de tipos, utiliza el modificador final. Esto evita la creación de subtipos a partir de una clase fuera de la librería actual. Desautorizar tanto la herencia como la implementación evita la creación de subtipos por completo. Esto garantiza:

  • Puedes agregar cambios incrementales de forma segura a la API.
  • Puedes llamar a los métodos de instancia sabiendo que no han sido sobrescritos en una subclase de terceros.

Las clases final se pueden extender o implementar dentro de la misma librería. El modificador final engloba los efectos de base, y por lo tanto cualquier subclase también debe ser marcada como base, final o sealed.

a.dart
dart
final class Vehicle {
  void moveForward(int meters) {
    // ...
  }
}
b.dart
dart
import 'a.dart';

// Can be constructed.
Vehicle myVehicle = Vehicle();

// ERROR: `Vehicle` can't be extended in a different library
// because it is marked `final`.
class Car extends Vehicle {
  int passengers = 4;
  // ...
}

// ERROR: `Vehicle` can't be implemented in a different library because
// it is marked `final`.
class MockVehicle implements Vehicle {
  @override
  void moveForward(int meters) {
    // ...
  }
}

sealed

#

Para crear un conjunto conocido y enumerable de subtipos, utiliza el modificador sealed. Esto te permite realizar un switch sobre esos subtipos que está estáticamente garantizado como exhaustivo (exhaustive).

El modificador sealed evita que una clase sea extendida o implementada fuera de su propia librería. Las clases selladas (sealed) son implícitamente abstract.

  • No se pueden construir a sí mismas.
  • Pueden tener constructores factory.
  • Pueden definir constructores para que sus subclases los utilicen.

Sin embargo, las subclases de clases selladas no son implícitamente abstractas.

El compilador es consciente de cualquier posible subtipo directo porque solo pueden existir en la misma librería. Esto le permite al compilador alertarte cuando un switch no maneja exhaustivamente todos los subtipos posibles en sus casos:

dart
sealed class Vehicle {}

class Car extends Vehicle {}

class Truck implements Vehicle {}

class Bicycle extends Vehicle {}

// ERROR: `Vehicle` can't be instantiated because
// it is marked `sealed` and therefore, implicitly abstract.
Vehicle myVehicle = Vehicle();

// Subclasses of a sealed class can be instantiated unless also restricted.
Vehicle myCar = Car();

extension VehicleSounds on Vehicle {
  String get sound {
    // ERROR: The switch does not exhaustively account for
    // all possible objects of type `Vehicle`.
    // In this example, a `Vehicle` with a run-time type of `Bicycle`
    // would not match any of the cases.
    return switch (this) {
      Car() => 'vroom',
      Truck() => 'VROOOOMM',
    };
  }
}

Si no deseas un cambio exhaustivo (exhaustive switching), o deseas poder agregar subtipos más adelante sin romper la API, utiliza el modificador final. Para una comparación más detallada, lee sealed frente a final.

Combinación de modificadores

#

Puedes combinar algunos modificadores para obtener restricciones por capas. La declaración de una clase puede ser, en orden:

  1. (Opcional) abstract, que describe si la clase puede contener miembros abstractos y evita la instanciación.
  2. (Opcional) Uno de base, interface, final o sealed, que describe las restricciones sobre otras librerías que subtipean la clase.
  3. (Opcional) mixin, que describe si la declaración se puede mezclar (mixed in).
  4. La palabra clave class en sí misma.

No se pueden combinar algunos modificadores porque son contradictorios, redundantes o mutuamente excluyentes:

  • abstract con sealed. Una clase sealed es implícitamente abstract.
  • interface, final o sealed con mixin. Estos modificadores de acceso evitan que se mezclen (mixing in).

Para obtener más orientación sobre cómo se pueden combinar los modificadores de clases, consulta la Referencia de modificadores de clases.