Clases y Modificadores de Acceso
Implementa el paradigma de Programación Orientada a Objetos con soporte completo de tipos. Descubre cómo encapsular datos usando modificadores de acceso, crear clases abstractas y manejar la herencia.
La Programación Orientada a Objetos (POO) cobra una nueva dimensión en TypeScript. Mientras que JavaScript moderno permite agrupar datos y comportamientos en clases, TypeScript eleva este paradigma al introducir un sistema de tipado estricto y herramientas avanzadas de arquitectura de software.
A lo largo de este capítulo, aprenderás a estructurar tus programas utilizando planos robustos y reutilizables. Descubrirás cómo encapsular la información mediante modificadores de acceso, cómo garantizar la inmutabilidad de los datos, el modo de extender lógicas complejas a través de la herencia y cómo definir contratos inquebrantables implementando interfaces y abstracciones.
7.1 Creación de clases y constructores
En JavaScript moderno (a partir de ES6), las clases proporcionan una sintaxis mucho más limpia y estructurada para implementar la Programación Orientada a Objetos (POO) basada en prototipos. TypeScript adopta por completo esta sintaxis y la eleva a un nuevo nivel al introducir un sistema de tipado estricto. Esto nos permite definir de forma explícita qué datos componen a un objeto y garantizar que las interacciones con sus métodos sean completamente seguras en tiempo de compilación.
Una clase actúa como un plano o plantilla para crear objetos. Define la estructura de los datos (mediante propiedades) y el comportamiento (mediante métodos) que tendrán las instancias creadas a partir de ella.
Anatomía de una clase en TypeScript
A diferencia de JavaScript tradicional, donde las propiedades suelen declararse directamente dentro del constructor, en TypeScript es obligatorio anunciar o declarar las propiedades de la clase y sus tipos antes de poder utilizarlas.
El siguiente diagrama de texto plano ilustra la estructura básica de una clase con sus componentes principales:
TEXT
Veamos cómo se traduce esta estructura a código real de TypeScript:
TypeScript
El rol de las propiedades y la inicialización estricta
Cuando declaras una propiedad en una clase, TypeScript aplicará las reglas de validación estricta (siempre que la opción strictPropertyInitialization esté activa en tu archivo tsconfig.json). Esto significa que toda propiedad declarada debe ser inicializada obligatoriamente de alguna de las siguientes formas:
- En la misma línea de la declaración: Proporcionando un valor por defecto.
- Dentro del constructor: Asignándole un valor recibido mediante los parámetros.
Si una propiedad no se inicializa en ninguno de estos puntos, el compilador arrojará un error de tipado:
TypeScript
Si por razones de arquitectura sabes que una propiedad se inicializará de forma externa (por ejemplo, a través de una librería de inyección de dependencias o un método de inicialización diferida), puedes usar el operador de aserción de asignación definitiva (!). Esto le indica a TypeScript que confíe en que la variable tendrá un valor en tiempo de ejecución:
TypeScript
El Constructor y la inicialización de instancias
El método constructor es una función especial que se ejecuta automáticamente en el momento exacto en que creamos una nueva instancia de la clase utilizando la palabra clave new. Su objetivo primordial es preparar el estado inicial del objeto.
Al igual que cualquier otra función en TypeScript (como viste en el Capítulo 4), los parámetros del constructor admiten tipado completo, valores por defecto y parámetros opcionales.
TypeScript
Métodos en las clases
Los métodos definen las acciones que un objeto puede realizar. En TypeScript, los métodos de una clase se declaran omitiendo la palabra clave function. La gran ventaja frente a JavaScript es que puedes aplicar tipado tanto a los parámetros del método como a su valor de retorno, asegurando que el flujo de datos dentro de tu lógica orientada a objetos sea predecible.
Dentro de cualquier método o del propio constructor, se utiliza la palabra clave reservada this. this hace referencia directa a la instancia específica del objeto que ha invocado el método, permitiendo acceder y modificar sus propiedades internas.
TypeScript
Inicialización resumida en propiedades (Property Showdown)
Aunque la sintaxis tradicional de declarar las propiedades al inicio de la clase y luego asignarlas en el constructor es muy clara, puede volverse repetitiva y generar mucho código redundante (boilerplate). TypeScript ofrece una alternativa sumamente potente llamada parámetros de propiedades o Parameter Properties, la cual estudiaremos a fondo en la sección 7.2 mediante el uso de modificadores de acceso, permitiendo declarar e inicializar campos de la clase en una única línea directamente dentro del constructor.
7.2 Modificadores: public, private, protected
En la programación orientada a objetos, la encapsulación es un principio fundamental que consiste en ocultar los detalles internos de cómo funciona un objeto y exponer solo lo que es verdaderamente necesario. Esto evita que el código externo altere el estado interno de un objeto de manera inesperada o incorrecta.
TypeScript introduce tres modificadores de acceso principales para controlar la visibilidad de las propiedades y métodos de una clase: public, private y protected.
El modificador public (Público)
Por defecto, si no especificas ningún modificador de acceso antes de una propiedad o un método, TypeScript asumirá que es public.
Los miembros marcados como public pueden ser accedidos desde cualquier parte de tu aplicación: desde dentro de la propia clase, desde clases que heredan de ella (clases hijas) y desde instancias externas.
TypeScript
Buenas prácticas: Aunque omitir
publices perfectamente válido, muchos desarrolladores prefieren escribirlo explícitamente para hacer el código más legible y dejar claras sus intenciones de diseño.
El modificador private (Privado)
Cuando marcas una propiedad o un método como private, restringes su acceso de forma estricta. Un miembro privado solo puede ser accedido o modificado dentro de la misma clase en la que fue definido. Ni las instancias externas ni las clases derivadas (hijas) pueden acceder a él.
TypeScript
TypeScript private vs JavaScript #private
Es importante destacar que el modificador private de TypeScript es una comprobación en tiempo de compilación. Cuando TypeScript se compila a JavaScript puro, esa restricción desaparece del código resultante.
Si necesitas privacidad dura también en tiempo de ejecución (que el navegador o Node.js bloqueen el acceso de forma nativa), puedes usar la sintaxis de campos privados de JavaScript ECMAScript utilizando el prefijo #:
TypeScript
El modificador protected (Protegido)
El modificador protected actúa como un punto medio entre public y private. Los miembros protegidos no pueden ser accedidos desde instancias externas, pero sí son completamente accesibles desde dentro de la clase que los define y por cualquier clase que herede (hija) de ella.
Este modificador cobra total sentido cuando empezamos a trabajar con la herencia (la cual se detalla en la sección 7.4).
TypeScript
Resumen de visibilidad
El siguiente cuadro compara de manera visual los límites de acceso de cada modificador:
TEXT
Parámetros de Propiedades (Parameter Properties)
Como se adelantó al final de la lección anterior, declarar propiedades al inicio de la clase para luego asignarlas manualmente dentro del constructor genera código repetitivo. TypeScript soluciona esto de manera brillante combinando los modificadores de acceso con los parámetros del constructor.
Si antepones un modificador de acceso (public, private o protected) a un parámetro del constructor, TypeScript automáticamente hará tres cosas por ti tras bambalinas:
- Declarará la propiedad en la clase con ese tipo de dato.
- Recibirá el argumento cuando hagas el
new. - Asignará el valor al campo interno utilizando
this.propiedad = valor.
Observemos la diferencia drástica en el volumen de código:
Enfoque tradicional (Sin parámetros de propiedades)
TypeScript
Enfoque moderno y optimizado de TypeScript
TypeScript
Esta sintaxis limpia y compacta es ampliamente utilizada en el ecosistema de TypeScript (como en el desarrollo con Angular o NestJS) debido a que reduce sustancialmente las líneas de código de mantenimiento sin perder en ningún momento el tipado estricto ni la seguridad de la encapsulación.
7.3 Propiedades estáticas y de solo lectura
TypeScript añade un control todavía más fino sobre cómo se comportan las propiedades de nuestras clases mediante dos modificadores de comportamiento muy potentes: static y readonly. A diferencia de los modificadores de acceso (public, private, protected), que controlan quién puede ver los datos, estos modificadores controlan cómo y dónde existen esos datos, así como si pueden modificarse una vez creados.
Miembros estáticos con el modificador static
Por norma general, cuando defines propiedades o métodos en una clase, estos pertenecen a las instancias individuales (los objetos creados con new). Sin embargo, en muchas ocasiones necesitas definir variables o funciones que pertenezcan a la clase en sí misma, de modo que sean compartidas por igual por todas las instancias o sirvan como utilidades globales.
Para lograr esto utilizamos el modificador static.
TEXT
Propiedades estáticas
Una propiedad estática se almacena una sola vez en memoria, directamente vinculada a la función constructora de la clase, y no se duplica cada vez que instanciamos un objeto.
TypeScript
Métodos estáticos
Los métodos estáticos suelen utilizarse para crear funciones de utilidad que no dependen del estado de ninguna instancia específica.
TypeScript
Regla de oro de
static: Un método estático no puede acceder a propiedades no estáticas de la clase utilizando la palabra clavethis, debido a que el método se ejecuta en el contexto de la clase y no sobre un objeto instanciado.
Inmutabilidad con el modificador readonly
El modificador readonly (de solo lectura) permite proteger las propiedades de una clase para evitar que sus valores sean modificados después de haber sido inicializados. Esto es crucial cuando deseas asegurar la inmutabilidad de ciertos identificadores, configuraciones o conexiones críticas.
Una propiedad marcada con readonly solo puede recibir un valor en dos lugares específicos:
- En el momento exacto de su declaración en la parte superior de la clase.
- Dentro del método
constructor.
Cualquier intento de reasignar un valor fuera de estos dos puntos provocará un error inmediato en el compilador.
TypeScript
Combinación de modificadores
El poder de TypeScript radica en que puedes encadenar múltiples modificadores sobre una sola propiedad para diseñar arquitecturas de datos extremadamente seguras y expresivas. El orden correcto para declararlos es colocar primero el modificador de acceso, seguido del modificador de contexto (static) y, por último, el modificador de mutabilidad (readonly).
Veamos un ejemplo avanzado que combina todas estas herramientas en una estructura de configuración global e inmutable:
TypeScript
readonly en Parámetros de Propiedades (Parameter Properties)
Como aprendiste en la sección 7.2, los parámetros del constructor nos permiten ahorrar líneas de código redundantes. Esta característica es totalmente compatible con readonly. Puedes declarar e inicializar una propiedad inmutable en una sola línea de la siguiente manera:
TypeScript
7.4 Herencia de clases y abstracción
La programación orientada a objetos brilla especialmente cuando necesitamos modelar relaciones del mundo real donde unas entidades comparten características con otras, pero añaden comportamientos especializados. En TypeScript, implementamos estas relaciones mediante los conceptos de herencia (reutilizar y extender código) y abstracción (definir contratos estructurales que no pueden instanciarse directamente).
Herencia con la palabra clave extends
La herencia permite crear una nueva clase (llamada clase hija o subclase) basada en una clase existente (llamada clase padre o superclase). La clase hija hereda de forma automática todas las propiedades y métodos públicos o protegidos de la clase padre.
Para heredar de una clase, utilizamos la palabra clave extends.
TypeScript
Reglas críticas de super()
Cuando una clase hija define su propio método constructor, debe invocar a super() antes de intentar acceder a cualquier propiedad mediante this. La llamada a super() ejecuta el constructor de la clase padre y mapea correctamente los datos en memoria. Si olvidas ponerlo, TypeScript generará un error de compilación inmediato.
Sobrescritura de Métodos (Method Overriding)
Una clase hija no está obligada a usar los métodos del padre exactamente como fueron escritos. Si el comportamiento de la superclase no se ajusta por completo a las necesidades de la subclase, esta última puede sobrescribir (redefinir) el método manteniendo el mismo nombre.
TypeScript
La palabra clave
override: Aunque TypeScript permite sobrescribir métodos de forma implícita, el uso del modificadoroverridees una excelente práctica (y obligatoria bajo ciertas configuraciones del compilador). Le avisa explícitamente a otros desarrolladores que el método viene de la clase base y evita errores si el método del padre cambia de nombre en el futuro.
Abstracción con clases y métodos abstract
Las clases abstractas actúan estrictamente como moldes o directrices conceptuales para otras clases. No puedes crear instancias (hacer un new) directamente de una clase abstracta. Su único propósito en el ciclo de vida del software es ser heredadas.
Se definen anteponiendo la palabra clave abstract antes de class.
TEXT
Métodos abstractos
Dentro de una clase abstracta, puedes definir métodos abstractos. Estos métodos no contienen ninguna lógica ni cuerpo (no llevan llaves {}), únicamente especifican la firma del método (nombre, parámetros y tipo de retorno).
Cualquier clase no abstracta que herede de ella estará estrictamente obligada a implementar la lógica real de esos métodos abstractos.
TypeScript
Las clases abstractas son una de las herramientas de arquitectura de software más robustas de TypeScript, ya que te permiten establecer un control absoluto sobre el diseño de tus librerías y módulos, asegurando que tus compañeros de equipo o tú mismo sigan las mismas reglas de estructura de datos a medida que el sistema escala.
7.5 Implementación de interfaces en clases
En el Capítulo 6 aprendiste que las interfaces sirven para definir contratos estructurales que describen detalladamente la forma que debe tener un objeto. Cuando trasladamos este concepto a la Programación Orientada a Objetos, las interfaces se convierten en una herramienta extraordinaria para lograr el desacoplamiento. Permiten asegurar que una clase cumpla estrictamente con un conjunto de características (propiedades y métodos) sin obligarla a heredar de una clase padre específica.
Mientras que una clase solo puede heredar de una única superclase (herencia simple), una clase puede implementar múltiples interfaces simultáneamente, otorgándole una enorme flexibilidad al diseño de tus aplicaciones.
La palabra clave implements
Para indicarle a TypeScript que una clase debe someterse a la estructura definida por una interfaz, utilizamos la palabra clave implements. Al hacer esto, la clase asume el compromiso de dar una definición real a cada propiedad y método listado en la interfaz. Si falta alguno de ellos, el compilador generará un error inmediatamente.
TypeScript
Gracias a este diseño, puedes crear funciones o colecciones de datos basadas en la interfaz, permitiendo intercambiar la clase real en tiempo de ejecución sin alterar el resto de tu código:
TypeScript
Implementación de múltiples interfaces
Una de las grandes limitaciones de las clases abstractas es que una clase no puede heredar de varios padres a la vez. Las interfaces resuelven este problema por completo, ya que actúan como piezas de comportamiento modulares que puedes combinar separándolas por comas ,.
El siguiente diagrama en texto plano ilustra cómo una única clase puede adoptar múltiples contratos estructurales:
TEXT
Veamos cómo se escribe este patrón en TypeScript:
TypeScript
Interfaces frente a Clases Abstractas
Es común confundir cuándo usar una clase abstracta (Sección 7.4) y cuándo usar una interfaz. La diferencia clave radica en la presencia de lógica de ejecución:
- Usa Clases Abstractas cuando desees compartir código real y reutilizable (métodos concretos) entre varias clases relacionadas y establecer una jerarquía directa de herencia ("un perro es un animal").
- Usa Interfaces cuando únicamente quieras definir un plano conceptual o comportamiento sin aportar ninguna línea de lógica interna, uniendo clases que podrían no tener relación alguna entre sí ("un usuario y un botón pueden ser ambos Hacéclic").
Resumen del capítulo
En este Capítulo 7: Clases y Modificadores de Acceso, has adquirido los conocimientos fundamentales para dominar la Programación Orientada a Objetos (POO) bajo el estricto control de tipos de TypeScript:
- Creación y constructores (7.1): Aprendiste la anatomía de una clase, cómo declarar sus propiedades previamente y cómo inicializarlas de manera segura a través del método
constructor. - Modificadores de acceso (7.2): Analizaste el nivel de aislamiento de datos usando
public(acceso total),private(exclusivo de la propia clase) yprotected(permitido para clases hijas). También descubriste cómo compactar código usando Parameter Properties. - Control estático e inmutabilidad (7.3): Estudiaste cómo crear variables y funciones globales vinculadas a la clase en sí mediante
static, y cómo blindar datos contra modificaciones accidentales conreadonly. - Herencia y abstracción (7.4): Exploraste la extensión de lógica con
extends, la llamada obligatoria asuper(), la sobrescritura de métodos conoverride, y el diseño de moldes de arquitectura estrictos con clases y métodosabstract. - Implementación de interfaces (7.5): Descubriste cómo obligar a una clase a cumplir un contrato estructural mediante
implementsy la potencia de implementar múltiples interfaces simultáneamente para lograr un código altamente desacoplado y escalable.
© 2026 Esdocu. Contenido bajo licencia MIT.
Editar esta página