Implements y múltiples interfaces
La palabra clave implements establece un contrato entre una clase y una o varias interfaces. TypeScript verifica en tiempo de compilación que la clase proporcione todos los miembros que la interfaz exige:

interface Reproducible {
reproducir(): void
pausar(): void
detener(): void
obtenerDuracion(): number
}
class ReproductorAudio implements Reproducible {
private reproduciendo: boolean = false
private posición: number = 0
constructor(private duracionTotal: number) {}
reproducir(): void {
this.reproduciendo = true
console.log("Reproduciendo audio...")
}
pausar(): void {
this.reproduciendo = false
console.log(`Pausado en posición ${this.posición}`)
}
detener(): void {
this.reproduciendo = false
this.posición = 0
console.log("Detenido")
}
obtenerDuracion(): number {
return this.duracionTotal
}
}
Si la clase no implementa algún miembro de la interfaz, TypeScript genera un error de compilación:
// class ReproductorIncompleto implements Reproducible {
// reproducir(): void {} // Error: falta pausar, detener y obtenerDuracion
// }
La cláusula
implementssolo verifica que la clase cumple la forma de la interfaz. No genera automáticamente propiedades ni modifica el tipo de la clase. Cada miembro debe implementarse de forma explícita.
Implementación de múltiples interfaces
Una clase puede implementar varias interfaces separándolas con comas. Esto permite componer comportamientos sin recurrir a herencia múltiple:
interface Almacenable {
guardar(): boolean
eliminar(): boolean
}
interface Buscable {
buscar(criterio: string): string[]
}
interface Validable {
validar(): { válido: boolean; errores: string[] }
}
class Contacto implements Almacenable, Buscable, Validable {
constructor(
public nombre: string,
public email: string,
public telefono: string
) {}
guardar(): boolean {
console.log(`Guardando contacto: ${this.nombre}`)
return true
}
eliminar(): boolean {
console.log(`Eliminando contacto: ${this.nombre}`)
return true
}
buscar(criterio: string): string[] {
const campos = [this.nombre, this.email, this.telefono]
return campos.filter(campo => campo.includes(criterio))
}
validar(): { válido: boolean; errores: string[] } {
const errores: string[] = []
if (!this.nombre.trim()) errores.push("Nombre requerido")
if (!this.email.includes("@")) errores.push("Email inválido")
if (this.telefono.length < 9) errores.push("Telefono demasiado corto")
return { válido: errores.length === 0, errores }
}
}
const contacto = new Contacto("Maria", "maria@ejemplo.com", "612345678")
const validación = contacto.validar()
console.log(validación) // { válido: true, errores: [] }
contacto.guardar()
Las interfaces también pueden extenderse entre sí para construir contratos más complejos:
interface Entidad {
id: string
creadoEn: Date
}
interface EntidadActualizable extends Entidad {
actualizadoEn: Date
actualizar(datos: Record<string, unknown>): void
}
interface EntidadEliminable extends Entidad {
eliminadoEn: Date | null
eliminarLogicamente(): void
}
class Articulo implements EntidadActualizable, EntidadEliminable {
actualizadoEn: Date
eliminadoEn: Date | null = null
constructor(
public id: string,
public creadoEn: Date,
public titulo: string,
public contenido: string
) {
this.actualizadoEn = creadoEn
}
actualizar(datos: Record<string, unknown>): void {
if (datos["titulo"]) this.titulo = datos["titulo"] as string
if (datos["contenido"]) this.contenido = datos["contenido"] as string
this.actualizadoEn = new Date()
}
eliminarLogicamente(): void {
this.eliminadoEn = new Date()
}
}
Clases como tipos e instanceof
Tipado estructural con clases
TypeScript utiliza tipado estructural, lo que significa que dos tipos son compatibles si sus estructuras coinciden, independientemente de si están relacionados por herencia o implementación:
class Punto2D {
constructor(public x: number, public y: number) {}
}
class Coordenada {
constructor(public x: number, public y: number) {}
}
function distanciaAlOrigen(punto: Punto2D): number {
return Math.sqrt(punto.x ** 2 + punto.y ** 2)
}
const coord = new Coordenada(3, 4)
// Funciona porque Coordenada tiene la misma forma que Punto2D
console.log(distanciaAlOrigen(coord)) // 5
Las clases pueden usarse directamente como tipos para anotar parámetros, variables o retornos de funciones:
class Evento {
constructor(
public nombre: string,
public fecha: Date,
public participantes: number
) {}
esGrande(): boolean {
return this.participantes > 100
}
}
function programarEvento(evento: Evento): string {
const tamano = evento.esGrande() ? "grande" : "pequeño"
return `Evento ${tamano}: ${evento.nombre} el ${evento.fecha.toLocaleDateString()}`
}
const conferencia = new Evento("TypeScript Conf", new Date("2025-06-15"), 500)
console.log(programarEvento(conferencia))
Un objeto literal también puede satisfacer el tipo de una clase si tiene la misma estructura:
class Producto {
constructor(
public nombre: string,
public precio: number
) {}
aplicarDescuento(porcentaje: number): number {
return this.precio * (1 - porcentaje / 100)
}
}
function mostrarPrecio(producto: Producto): string {
return `${producto.nombre}: ${producto.precio} euros`
}
// Un objeto literal no tiene el método aplicarDescuento,
// pero si cumple con las propiedades requeridas
// No satisface la forma completa de Producto si se usan sus métodos
Comprobaciones con instanceof
El operador instanceof permite verificar en tiempo de ejecución si un objeto fue creado por un constructor específico. TypeScript aprovecha esta comprobación para estrechar el tipo automáticamente:
class Error404 {
constructor(public recurso: string) {}
obtenerMensaje(): string {
return `Recurso no encontrado: ${this.recurso}`
}
}
class Error500 {
constructor(public detalle: string) {}
obtenerMensaje(): string {
return `Error interno: ${this.detalle}`
}
}
class Error403 {
constructor(public permiso: string) {}
obtenerMensaje(): string {
return `Acceso denegado: falta permiso ${this.permiso}`
}
}
type ErrorApp = Error404 | Error500 | Error403
function manejarError(error: ErrorApp): string {
if (error instanceof Error404) {
return `404 - ${error.recurso} no existe`
}
if (error instanceof Error500) {
return `500 - Fallo del servidor: ${error.detalle}`
}
if (error instanceof Error403) {
return `403 - Necesitas: ${error.permiso}`
}
return "Error desconocido"
}
const error = new Error404("/api/usuarios/999")
console.log(manejarError(error)) // "404 - /api/usuarios/999 no existe"
El operador instanceof funciona con herencia, detectando tanto la clase concreta como las clases padre:
abstract class Transporte {
abstract velocidadMaxima(): number
}
class Avion extends Transporte {
velocidadMaxima(): number {
return 900
}
}
class Tren extends Transporte {
velocidadMaxima(): number {
return 300
}
}
const vehiculo: Transporte = new Avion()
console.log(vehiculo instanceof Transporte) // true
console.log(vehiculo instanceof Avion) // true
console.log(vehiculo instanceof Tren) // false
Patrones OOP con interfaces tipadas
Patrón Repository
El patrón Repository encapsula la lógica de acceso a datos detrás de una interfaz. Esto permite cambiar la implementación concreta sin modificar el código que la consume:
interface Identifiable {
id: string
}
interface Repository<T extends Identifiable> {
obtenerPorId(id: string): T | undefined
obtenerTodos(): T[]
crear(entidad: T): T
actualizar(id: string, datos: Partial<T>): T | undefined
eliminar(id: string): boolean
}
interface Usuario {
id: string
nombre: string
email: string
activo: boolean
}
class MemoryUsuarioRepository implements Repository<Usuario> {
private almacen: Map<string, Usuario> = new Map()
obtenerPorId(id: string): Usuario | undefined {
return this.almacen.get(id)
}
obtenerTodos(): Usuario[] {
return Array.from(this.almacen.values())
}
crear(entidad: Usuario): Usuario {
this.almacen.set(entidad.id, { ...entidad })
return entidad
}
actualizar(id: string, datos: Partial<Usuario>): Usuario | undefined {
const existente = this.almacen.get(id)
if (!existente) return undefined
const actualizado = { ...existente, ...datos, id }
this.almacen.set(id, actualizado)
return actualizado
}
eliminar(id: string): boolean {
return this.almacen.delete(id)
}
}
function listarUsuariosActivos(repo: Repository<Usuario>): Usuario[] {
return repo.obtenerTodos().filter(u => u.activo)
}
const repo = new MemoryUsuarioRepository()
repo.crear({ id: "1", nombre: "Ana", email: "ana@ejemplo.com", activo: true })
repo.crear({ id: "2", nombre: "Luis", email: "luis@ejemplo.com", activo: false })
repo.crear({ id: "3", nombre: "Eva", email: "eva@ejemplo.com", activo: true })
const activos = listarUsuariosActivos(repo)
console.log(activos.length) // 2
La función listarUsuariosActivos depende de la interfaz Repository<Usuario>, no de la implementación concreta. Esto permite sustituir MemoryUsuarioRepository por otra implementación sin cambiar la lógica:
class JsonUsuarioRepository implements Repository<Usuario> {
private datos: Usuario[]
constructor(jsonString: string) {
this.datos = JSON.parse(jsonString) as Usuario[]
}
obtenerPorId(id: string): Usuario | undefined {
return this.datos.find(u => u.id === id)
}
obtenerTodos(): Usuario[] {
return [...this.datos]
}
crear(entidad: Usuario): Usuario {
this.datos.push({ ...entidad })
return entidad
}
actualizar(id: string, datos: Partial<Usuario>): Usuario | undefined {
const indice = this.datos.findIndex(u => u.id === id)
if (indice === -1) return undefined
this.datos[indice] = { ...this.datos[indice], ...datos, id }
return this.datos[indice]
}
eliminar(id: string): boolean {
const longitudInicial = this.datos.length
this.datos = this.datos.filter(u => u.id !== id)
return this.datos.length < longitudInicial
}
}
Patrón Strategy
El patrón Strategy define una familia de algoritmos intercambiables. Cada estrategia implementa la misma interfaz, y el contexto trabaja con la interfaz sin conocer la implementación concreta:
interface EstrategiaOrdenamiento<T> {
ordenar(items: T[]): T[]
nombre: string
}
class OrdenamientoPorNombre implements EstrategiaOrdenamiento<{ nombre: string }> {
nombre = "Por nombre (A-Z)"
ordenar(items: { nombre: string }[]): { nombre: string }[] {
return [...items].sort((a, b) => a.nombre.localeCompare(b.nombre))
}
}
class OrdenamientoPorPrecio implements EstrategiaOrdenamiento<{ precio: number }> {
nombre = "Por precio (menor a mayor)"
ordenar(items: { precio: number }[]): { precio: number }[] {
return [...items].sort((a, b) => a.precio - b.precio)
}
}
class OrdenamientoPorFecha implements EstrategiaOrdenamiento<{ fecha: Date }> {
nombre = "Por fecha (mas reciente)"
ordenar(items: { fecha: Date }[]): { fecha: Date }[] {
return [...items].sort((a, b) => b.fecha.getTime() - a.fecha.getTime())
}
}
interface ArticuloTienda {
nombre: string
precio: number
fecha: Date
}
class Catalogo {
private items: ArticuloTienda[] = []
agregar(item: ArticuloTienda): void {
this.items.push(item)
}
listar(estrategia: EstrategiaOrdenamiento<ArticuloTienda>): ArticuloTienda[] {
console.log(`Ordenando: ${estrategia.nombre}`)
return estrategia.ordenar([...this.items])
}
}
const catalogo = new Catalogo()
catalogo.agregar({ nombre: "Monitor", precio: 350, fecha: new Date("2025-01-15") })
catalogo.agregar({ nombre: "Auriculares", precio: 89, fecha: new Date("2025-03-01") })
catalogo.agregar({ nombre: "Webcam", precio: 120, fecha: new Date("2025-02-10") })
const porPrecio = new OrdenamientoPorPrecio()
const resultado = catalogo.listar(porPrecio)
console.log(resultado.map(i => `${i.nombre}: ${i.precio}e`))
// ["Auriculares: 89e", "Webcam: 120e", "Monitor: 350e"]
Patrón Factory con clases tipadas
El patrón Factory encapsula la creación de objetos. Usando interfaces como tipo de retorno, el código consumidor permanece desacoplado de las clases concretas:
interface Notificacion {
enviar(destinatario: string, mensaje: string): boolean
obtenerTipo(): string
}
class NotificacionEmail implements Notificacion {
enviar(destinatario: string, mensaje: string): boolean {
console.log(`Email a ${destinatario}: ${mensaje}`)
return true
}
obtenerTipo(): string {
return "email"
}
}
class NotificacionPush implements Notificacion {
enviar(destinatario: string, mensaje: string): boolean {
console.log(`Push a ${destinatario}: ${mensaje}`)
return true
}
obtenerTipo(): string {
return "push"
}
}
class NotificacionSlack implements Notificacion {
enviar(destinatario: string, mensaje: string): boolean {
console.log(`Slack a ${destinatario}: ${mensaje}`)
return true
}
obtenerTipo(): string {
return "slack"
}
}
type TipoNotificacion = "email" | "push" | "slack"
class NotificacionFactory {
static crear(tipo: TipoNotificacion): Notificacion {
switch (tipo) {
case "email":
return new NotificacionEmail()
case "push":
return new NotificacionPush()
case "slack":
return new NotificacionSlack()
}
}
}
function notificar(tipo: TipoNotificacion, destinatario: string, mensaje: string): void {
const notificacion = NotificacionFactory.crear(tipo)
notificacion.enviar(destinatario, mensaje)
}
notificar("email", "usuario@ejemplo.com", "Bienvenido")
notificar("slack", "#general", "Deploy completado")
const notificaciones: Notificacion[] = [
NotificacionFactory.crear("email"),
NotificacionFactory.crear("push"),
NotificacionFactory.crear("slack")
]
for (const n of notificaciones) {
console.log(`Tipo: ${n.obtenerTipo()}`)
}
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, TypeScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de TypeScript
Explora más contenido relacionado con TypeScript y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Dominar la palabra clave implements para conectar clases con interfaces. Implementar múltiples interfaces en una misma clase. Usar clases como tipos y comprender la tipificación estructural. Aplicar instanceof para comprobaciones en tiempo de ejecución. Construir patrones OOP practicos como Repository y Strategy con interfaces tipadas.