Interfaces y comparación con Clases. Y Shapes
Una interface en TypeScript sirve para definir la forma (estructura) que debe tener un objeto. Es como un contrato: si un objeto 'implementa' una interface, está obligado a tener exactamente las propiedades y tipos definidos en ella. Pero es sólo eso. Es decir, no tienen lógica interna, es sólo marcar una estructura a respetar.
Interfaces
Una interface en TypeScript sirve para definir la forma (estructura) que debe tener un objeto. Es como un contrato: si un objeto "implementa" una interface, está obligado a tener exactamente las propiedades y tipos definidos en ella. Pero es sólo eso. Es decir, no tienen lógica interna, es sólo marcar una estructura a respetar.
Sirven para:
- Definir una estructura en objetos, clases o arrays.
- Definir la forma de funciones (qué deben recibir y qué deben retornar).
- Tener reutilización y consistencia en tipos grandes o repetitivos.
Veamos un ejemplo básico:
interface Persona {
nombre: string;
apellido?: string; // El " ? " significa que puede o no tener apellido
edad: number;
saludar(): void;
}
const persona1: Persona = {
nombre: "Lucas",
edad: 23,
saludar() {
console.log("Hola!");
},
};TypeScript se asegura de que persona1 tenga todas las propiedades que la interface requiere.
Vamos a ver un ejemplo de interfaz de función:
interface Operacion {
(a: number, b: number): number;
}
const suma: Operacion = (x, y) => x + y;En este caso, como suma implementa la interfaz Operacion, está obligado a cumplir con lo que tiene la interfaz, que en este caso es una función que recibe 2 parámetros de tipo number, y que retorna un number.
Veamos otro ejemplo, usando objetos:
interface Diccionario {
[clave: string]: string;
}
const dic: Diccionario = {
hola: "hello",
mundo: "world",
};En este caso, creamos una interfaz Diccionario, la cual indica que va a tener propiedades string con valores string también. Después, le implementamos Diccionario a "dic", para que cumpla con esas reglas.
Las interfaces también se pueden heredar, así:
interface Animal {
nombre: string;
}
interface Perro extends Animal {
raza: string;
}
const miPerro: Perro = {
nombre: "Toby",
raza: "Labrador",
};Clases implementando interfaces
Las clases pueden implementar interfaces, de la siguiente manera:
interface SerVivo {
respirar(): void;
}
class Humano implements SerVivo {
respirar() {
console.log("Respirando...");
}
}En este caso, la clase Humano implementa a SerVivo, eso significa que Humano está obligado a tener un método respirar() y que devuelva void. Es decir, tiene ese contrato firmado y debe cumplirlo o da error.
¿Interfaces o clases? ¿Cuándo usar cada una?
| Característica | Interfaces | Clases |
|---|---|---|
| Únicamente definen estructura | ✅ Sí | ❌ No (tienen lógica también) |
| Se usan para tipar objetos y contratos | ✅ Sí | ⚠️ Posible, pero no ideal |
| Pueden extender otras interfaces | ✅ Sí | ✅ Sí |
| Soportan implementación múltiple | ✅ Sí | ❌ No (solo una clase base) |
| Tienen lógica interna (constructores, métodos) | ❌ No | ✅ Sí |
| Se instancian con new | ❌ No | ✅ Sí |
Conviene usar interfaces cuando:
- Solo necesitamos definir la forma de un objeto (como un contrato).
- Queremos que varias clases u objetos cumplan con la misma estructura.
- Necesitamos tipar funciones, objetos, arrays, etc.
- Estamos trabajando con tipos de datos puros, sin necesidad de lógica interna.
Conviene usar clases cuando:
- Necesitamos crear instancias (new Clase()).
- Necesitamos lógica, métodos, constructores.
- Estamos trabajando con POO y jerarquías de herencia reales.
- Queremos encapsular comportamientos y estado.
Resumen visual
// Interface → define estructura
interface Usuario {
nombre: string;
edad: number;
}
// Clase → define estructura y lógica
class Usuario {
constructor(public nombre: string, public edad: number) {}
saludar() {
console.log(`Hola, soy ${this.nombre}`);
}
}Shapes en TypeScript
Shape = Forma/FormatoLas clases, las interfaces y los métodos tienen SHAPES. Es decir, formatos.
Veamos una clase Persona:
class Persona {
name: string;
constructor(name: string) {
this.name = name;
}
getName(): string {
return this.name;
}
setName(name: string): void {
this.name = name;
}
}El scope de la clase Persona (lo que está entre llaves {}), es su Shape.
Ahora, veamos una interfaz llamada PersonaInterface:
interface PersonaInterface {
name: string;
getName(): string;
setName(name: string): void;
}Como podemos ver, la clase Persona y la interfaz PersonaInterface tienen el mismo formato, es decir, el mismo shape. Esto significa que nosotros podemos hacer esto tranquilamente:
const persona: Persona = new Persona(name: "Miguel");
let personaPosible: PersonaInterface = persona;Eso no dará error, funciona bien. Y es así ya que la clase y la interfaz tienen el mismo shape. De hecho, si yo creara una clase llamada Persona2 y le copio y pego el mismo shape que tiene la clase Persona, voy a poder hacer exactamente lo mismo. Hay que saber que esto es posible y no imposible.