Visibilidad y Encapsulación: El Modelo de Librerías en Dart
En Dart, la privacidad no se gestiona con palabras clave como public, private o protected, sino mediante el alcance de la librería. Un identificador (que puede ser una variable, un método, una clase o un constructor) se vuelve privado a la librería si su nombre comienza con un guion bajo (_). Es vital entender que, en Dart, una librería suele ser un único archivo .dart, aunque puede estar compuesta por varios archivos conectados mediante la directiva part y part of.
Este diseño permite que los detalles de implementación se compartan libremente entre componentes que forman parte de la misma unidad lógica, sin exponerlos a quienes consumen el paquete. Si intentas acceder a un miembro con _ desde un archivo distinto, el compilador te lanzará un error, sin importar si ambos archivos pertenecen al mismo paquete. Para controlar qué elementos de una librería importada llegan a tu archivo, utilizas show (para incluir solo lo necesario) o hide (para excluir algo específico). Cuando necesitas evitar colisiones de nombres entre dos librerías que comparten clases con el mismo nombre, utilizas el prefijo as para crear un espacio de nombres.
Si intentas implementar un patrón de “clase privada” (estilo Java) usando _ y fallas en entender que la privacidad es a nivel de archivo, terminarás con una API con fugas de abstracción o con errores de compilación imposibles de resolver al intentar mover clases a archivos separados.
import 'dart:math' as math;
// --- MÓDULO DE SEGURIDAD (Simulando una librería) ---
class Vault {
// Campo privado: solo es visible dentro de este archivo/librería.
String _secretKey;
// Constructor privado: impide que alguien haga 'new Vault()' desde fuera.
// Esto nos obliga a usar un método controlado para la creación.
Vault._internal(this._secretKey);
// Factory pattern: la única vía pública para obtener una instancia.
factory Vault.create(String key) {
return Vault._internal(key);
}
void info() => print('Vault operando con llave de ${_secretKey.length} caracteres.');
}
// Esta clase está en el mismo archivo, por lo tanto, pertenece a la misma librería
// que Vault y puede acceder a sus miembros privados sin problemas.
class VaultAuditor {
void audit(Vault vault) {
// Acceso directo a _secretKey: permitido porque estamos en la misma librería.
print('Auditoría interna: La clave es ${_getSecret(vault)}');
}
// Método auxiliar para demostrar el acceso interno
String _getSecret(Vault v) => v._secretKey;
}
// --- CONSUMIDOR (Main) ---
void main() {
// 1. Uso de factory: Correcto.
final myVault = Vault.create('super-secreto-2024');
myVault.info();
// 2. Auditoría: Funciona porque VaultAuditor y Vault comparten el mismo archivo.
final auditor = VaultAuditor();
auditor.audit(myVault);
// 3. Intento de acceso privado:
// Si este código estuviera en un archivo separado, la siguiente línea
// daría un error de compilación: "The getter '_secretKey' isn't defined."
// print(myVault._secretKey);
// 4. Uso de 'as' para evitar colisiones de nombres:
final randomNum = math.Random().nextInt(100);
print('Número aleatorio: $randomNum');
}
Desglose del código
Fíjate en cómo Vault utiliza un constructor _internal. Al tener el prefijo _, el constructor se vuelve privado a la librería. Esto es un patrón común para implementar interfaces controladas: no permites que el usuario instancie la clase de cualquier manera, sino que lo obligas a usar el factory Vault.create, permitiéndote añadir lógica de validación o gestión de estado antes de devolver la instancia.
La clase VaultAuditor es la prueba de fuego de la privacidad de librería. Aunque Vault parece “encapsulada” para el mundo exterior, VaultAuditor puede entrar en sus entrañas y leer _secretKey sin problemas porque residen en el mismo ámbito. Si moviéramos VaultAuditor a un archivo llamado auditor.dart, la línea v._secretKey dentro de audit dejaría de compilar inmediatamente.
En el main, el uso de import 'dart:math' as math; es fundamental para la claridad. Si dart:math y otra librería que importaras tuvieran una función llamada sqrt(), sin el prefijo math., el compilador no sabría cuál usar. Finalmente, si estuvieras trabajando en un proyecto web con compilación a JS, podrías usar import 'package:big_library/big_library.dart' deferred as big; para que esa librería solo se descargue cuando realmente la necesites (lazy loading).
El error frecuente
Un error común para quienes vienen de lenguajes con visibilidad de clase es pensar que _miVariable es privada para la clase MiClase. Si defines una clase con un miembro _id y luego creas otra clase Helper en el mismo archivo para manipularla, Helper podrá tocar _id sin restricciones. El peligro real ocurre cuando intentas refactorizar ese código moviendo Helper a un archivo nuevo; de repente, tu código “rompe” no porque la lógica esté mal, sino porque has roto la visibilidad de librería, convirtiendo un miembro que antes era accesible (porque compartía archivo) en uno estrictamente privado para su archivo original.
N° 118