
TypeScript: Interfaces y contratos de tipos
- Dacadev
- Programación
- 18 de junio de 2026
Tabla de Contenido
Note
Quinto artículo de la serie sobre TypeScript. Estudiaremos las interfaces como contratos extensibles, su herencia con extends, su implementación en clases con implements, y sus diferencias frente a los alias de tipo.
Una interface en TypeScript define la forma que deben cumplir los objetos. A primera vista se parece a un alias de tipo, pero su rasgo distintivo es la extensibilidad: las interfaces están diseñadas para componerse, heredarse y servir como base de contratos más complejos. Analicemos cuándo y cómo usarlas.
Bases: definir y extender interfaces
Una interface declara la estructura de un objeto. Con extends, una interface puede heredar de otra y ampliar su contrato:
interface Hero {
name: string;
age?: number;
powers: string[];
getName?: () => string;
}
interface Avenger extends Hero {
marvel: string;
}
let flash: Hero = {
name: 'Barry Allen',
age: 24,
powers: ['super velocidad', 'viajar en el tiempo']
}
Avenger hereda todos los miembros de Hero y añade marvel. Esta composición es la base del diseño orientado a contratos.
Estructuras complejas: interfaces anidadas
Las interfaces pueden referenciarse entre sí para modelar estructuras de datos compuestas:
interface Client {
name: string;
age?: number;
address?: Address;
}
interface Address {
id: number;
zip: string;
city: string;
}
const client: Client = {
name: 'David',
address: {
id: 125,
zip: 'KYD 234',
city: 'Chía'
}
}
const client2: Client = {
name: 'Melissa',
age: 30
}
Client referencia a Address como un campo opcional, lo que permite componer entidades de dominio sin acoplarlas en una sola definición monolítica.
Métodos en la interfaz
Al igual que los alias de tipo, las interfaces pueden declarar métodos como parte del contrato:
interface Client {
name: string;
age?: number;
address?: Address;
getFullAddress(id: string): void;
}
Info
Declarar métodos en una interface es válido, pero implica que el objeto que la cumpla debe implementarlos. En la práctica, cuando un contrato requiere comportamiento, lo más limpio es implementarlo mediante una clase.
Implementar interfaces en clases
Una clase adopta uno o varios contratos con la palabra clave implements. Esto garantiza, en compilación, que la clase provee todo lo que el contrato exige:
interface Xmen {
name: string;
realName: string;
mutantPower(id: number): string;
}
interface Human {
age: number;
}
class Mutant implements Xmen, Human {
public age: number;
public name: string;
public realName: string;
mutantPower(id: number): string {
return 'Hola mundo';
}
}
Mutant implementa simultáneamente Xmen y Human. Si omitiera cualquier miembro requerido, el compilador lo rechazaría.
Interfaces para tipar funciones
Aunque es poco frecuente, una interface puede describir la firma de una función antes de asignarle una implementación:
interface AddTwoNumbers {
(a: number, b: number): number;
}
let addNumbers: AddTwoNumbers;
addNumbers = (a: number, b: number): number => a + b
Interfaces frente a alias de tipo
Esta es la decisión de diseño más común. Las tres diferencias clave:
1. Composición
Los alias de tipo componen mediante operaciones de conjuntos (|, &); las interfaces componen mediante extends:
type Food = { calories: number; tasty: boolean }
type Sushi = Food & { salty: boolean }
interface FoodI { calories: number; tasty: boolean }
interface SushiI extends FoodI { salty: boolean }
2. Restricción al extender
Al extender una interface, no puedes cambiar el tipo de un miembro heredado, solo añadir o restringir de forma compatible. Una incompatibilidad genera error:
interface A {
good(x: number): string
bad(x: number): string
}
interface B extends A {
good(x: string | number): string
bad(x: string): string // Error TS2430: 'B' extiende incorrectamente 'A'.
}
3. Declaration merging
Varias interfaces con el mismo nombre en el mismo ámbito se fusionan automáticamente; varios alias de tipo con el mismo nombre producen un error de compilación:
interface User { name: string }
interface User { age: number } // se fusiona
let a: User = { name: 'Ashley', age: 30 }
type User = { name: string } // Error TS2300: identificador duplicado
type User = { age: number } // Error TS2300
El declaration merging respeta los tipos existentes: no puedes redefinir un miembro ya declarado con otro tipo.
flowchart TD
Q{¿Necesitas extensibilidad,
implements o merging?}
Q -- Sí --> I[Usa interface]
Q -- "No (uniones, tuplas, mapeos)" --> T[Usa type]
Implementación con readonly y múltiples contratos
Las interfaces admiten propiedades readonly y permiten que una clase cumpla varios contratos a la vez:
interface Animal {
readonly name: string
eat(food: string): void
sleep(hours: number): void
}
interface Feline {
meow(): void
}
class Cat implements Animal, Feline {
name = 'Whiskers'
eat(food: string) { console.info('Ate some', food) }
sleep(hours: number) { console.info('Slept for', hours, 'hours') }
meow() { console.info('Meow') }
}
Conclusión
Las interfaces son el mecanismo de TypeScript para definir contratos extensibles y verificables. Elige una interface cuando necesites herencia con extends, implementación en clases con implements o declaration merging; reserva los alias de tipo para uniones, intersecciones, tuplas y tipos mapeados.
En el próximo artículo profundizaremos en las clases, la herencia, los modificadores de acceso y los patrones de diseño que habilitan.


