Saltar al contenido

Lección 13 de 20 · Lectura y práctica

Diseñar Interfaces Públicas para Librerías

Concepto claveDiseñar interfaces públicas para librerías en TypeScript es como crear el plano arquitectónico de un edificio que otros desarrolladores usarán. La interfaz pública define cómo los usuarios interactúan con tu código,.

Del concepto a la práctica

Taller de esta lección

Una interfaz pública debe exponer la intención mínima del consumidor. Si una librería de caché obliga a conocer su almacenamiento interno, el contrato se vuelve frágil.

Ejemplo resuelto

ts
export interface Cache<T> {
  get(key: string): T | undefined;
  set(key: string, value: T): void;
}
class MemoryCache<T> implements Cache<T> {
  private data = new Map<string, T>();
  get(key: string) { return this.data.get(key); }
  set(key: string, value: T) { this.data.set(key, value); }
}
const cache: Cache<number> = new MemoryCache();
cache.set("count", 4);
console.log(cache.get("count")); // 4

Pruébalo

  1. Consume Cache<string> sin acceder a data.

  2. Guarda "ok" bajo status.

  3. Intenta guardar un número.

Comprueba tu resultado

El ejemplo inicial lee4; tu Cache<string> debe leer "ok" bajo status y rechazar un número al compilar.

Error frecuente: exportar la clase concreta y sus campos privados como parte del uso normal. Corrígelo exponiendo la interfaz mínima; puedes reemplazar MemoryCache sin cambiar al consumidor.

get devuelve undefined cuando la clave no existe, así que el consumidor no debe usar métodos del valor sin verificarlo. Este Map es memoria del proceso y no conserva datos tras reiniciar ni comparte entradas entre servidores. private es privacidad de TypeScript, no cifrado ni autorización de datos. Define una política de tamaño o expiración si el ejercicio evoluciona a una caché real; la interfaz mínima actual no promete ninguna.

Diagrama de decisión con ramas Sí y No y un bucle que procesa cada elemento de una lista.
CursaloMapa de práctica
Cómo avanza un programa

Una condición decide qué camino ejecuta el programa. Un bucle repite una operación mientras queden elementos, y termina cuando la lista se agota. Separar ambas ideas ayuda a seguir el flujo y comprobar casos límite.

Ver la infografía en tamaño completo ↗
Fuentes para profundizar

Concepto clave

Diseñar interfaces públicas para librerías en TypeScript es como crear el plano arquitectónico de un edificio que otros desarrolladores usarán. La interfaz pública define cómo los usuarios interactúan con tu código, mientras que la implementación interna permanece oculta. En TypeScript avanzado, esto implica usar tipos genéricos, tipos condicionales y tipos mapeados para crear APIs flexibles pero seguras.

Imagina que construyes una librería de validación de formularios. Los usuarios solo necesitan saber cómo llamar a las funciones de validación y qué errores pueden recibir, no cómo se implementa cada regla internamente. Una buena interfaz pública actúa como un contrato: establece expectativas claras, reduce errores en tiempo de compilación y facilita el mantenimiento a largo plazo. En arquitecturas complejas, este diseño es crucial para la escalabilidad y la colaboración en equipos grandes.

Cómo funciona en la práctica

Vamos a construir una interfaz pública para una librería de caché tipada. Empezamos definiendo tipos genéricos que permitan flexibilidad sin perder seguridad.

// Definición de tipos base
type CacheConfig = {
  maxSize: number;
  defaultTTL: number;
  serialize?: (value: T) => string;
  deserialize?: (data: string) => T;
};

// Interfaz pública principal
interface TypedCache {
  set(key: K, value: V, ttl?: number): void;
  get(key: K): V | undefined;
  delete(key: K): boolean;
  clear(): void;
  has(key: K): boolean;
  keys(): K[];
  values(): V[];
}

Ahora, implementamos una clase que exponga solo esta interfaz, ocultando los detalles internos como el almacenamiento en memoria o la lógica de expiración. Usamos tipos condicionales para métodos opcionales:

type CacheMethod = T extends { serialize: infer S } ? S : never;

class MemoryCache implements TypedCache {
  private storage: Map;
  private config: CacheConfig;

  constructor(config: CacheConfig) {
    this.storage = new Map();
    this.config = config;
  }

  set(key: K, value: V, ttl?: number): void {
    const expires = Date.now() + (ttl || this.config.defaultTTL);
    this.storage.set(key, { value, expires });
  }

  get(key: K): V | undefined {
    const item = this.storage.get(key);
    if (!item || item.expires < Date.now()) {
      this.storage.delete(key);
      return undefined;
    }
    return item.value;
  }

  // Otros métodos implementados aquí
}

Caso de estudio

Considera una librería para manejar solicitudes HTTP con tipado fuerte. Queremos que los usuarios puedan definir tipos para las respuestas y parámetros, pero la interfaz debe ser intuitiva.

Una interfaz bien diseñada reduce los bugs en producción en hasta un 40%, según estudios de proyectos open-source.
// Interfaz pública
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';

type RequestOptions = {
  params?: TParams;
  body?: TBody;
  headers?: Record;
};

interface TypedHttpClient {
  request(
    method: HttpMethod,
    url: string,
    options?: RequestOptions
  ): Promise;
}

// Uso por el consumidor
type UserResponse = { id: number; name: string; email: string };
type UserParams = { userId: number };

const client: TypedHttpClient = new HttpClient();
const user = await client.request('GET', '/api/user', {
  params: { userId: 123 }
});
// user está tipado como UserResponse, evitando errores comunes

Esta interfaz usa tipos genéricos para TResponse, TParams y TBody, permitiendo a los usuarios especificar tipos concretos mientras mantienen la flexibilidad. La implementación interna maneja la lógica de red, pero la interfaz pública solo expone lo necesario.

Errores comunes

  • Exponer detalles de implementación: Incluir métodos internos en la interfaz pública, lo que acopla a los usuarios a cambios futuros. Solución: Usa clases privadas o módulos internos.
  • Tipos demasiado amplios: Usar any o unknown en lugares donde tipos específicos mejorarían la seguridad. Solución: Aplica tipos genéricos con restricciones (T extends SomeType).
  • Falta de documentación de tipos: No documentar cómo los tipos afectan el comportamiento, llevando a malentendidos. Solución: Usa comentarios JSDoc o archivos d.ts separados.
  • Sobrecarga de opciones: Crear interfaces con docenas de parámetros opcionales, dificultando el uso. Solución: Agrupa opciones en objetos tipados y usa valores por defecto.
  • Ignorar la ergonomía del desarrollador: Diseñar interfaces complejas que requieren mucho boilerplate. Solución: Proporciona funciones helper y tipos utilitarios (Partial, Pick).

Checklist de dominio

  1. ¿La interfaz pública oculta todos los detalles de implementación innecesarios?
  2. ¿Usas tipos genéricos para permitir flexibilidad sin sacrificar seguridad de tipos?
  3. ¿Los métodos y propiedades tienen nombres claros y descriptivos?
  4. ¿Incluyes tipos para errores y casos límite en la interfaz?
  5. ¿Has probado la interfaz con ejemplos del mundo real para validar su usabilidad?
  6. ¿Documentas los tipos y su comportamiento en comentarios o archivos separados?
  7. ¿La interfaz es compatible con versiones anteriores cuando sea posible?

Diseña una interfaz pública para una librería de logging tipada

Crea una interfaz pública en TypeScript para una librería de logging que permita a los usuarios registrar mensajes con diferentes niveles de severidad y metadatos tipados. Sigue estos pasos:

  1. Define un tipo LogLevel con los valores: 'debug', 'info', 'warn', 'error', 'fatal'.
  2. Crea una interfaz TypedLogger con métodos para cada nivel de log (ej: debug, info, etc.) que acepten un mensaje string y un objeto opcional de metadatos.
  3. Usa tipos genéricos para permitir que los metadatos sean tipados por el usuario, pero con un valor por defecto de Record.
  4. Añade un método child que cree un nuevo logger con metadatos adicionales, usando tipos mapeados para combinar los tipos.
  5. Implementa una clase ConsoleLogger que implemente esta interfaz, registrando en la consola, pero no expongas los detalles internos en la interfaz.
  6. Escribe un ejemplo de uso que muestre cómo un consumidor podría usar tu logger con tipos personalizados.
Pistas
  • Usa tipos condicionales para manejar los metadatos opcionales en el método child.
  • Considera usar un tipo genérico con restricción para los metadatos, como T extends Record.
  • Recuerda que la interfaz debe ser fácil de usar; prueba con un ejemplo simple antes de añadir complejidad.

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.

Sigue con TypeScript Avanzado: Patrones Genéricos y Tipado Profundo para Arquitecturas Complejas

Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.

Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.

01 / ELEGIR UNA RUTA

El próximo paso

Aprende IA mientras construyes algo que sí necesitas.

Empieza por el curso inicial o explora el catálogo para encontrar una práctica alineada con tu trabajo.