typeScript (3): clases

Introducción a las clases en ts (y js por extensión).

Stanley Boxer

archivado en: JavaScript / 30 agosto, 2016 / taller:

Comienzo esta tercera entrada de la serie dedicada a typeScript con un aviso preliminar. Trataré de clases y como es posible que los programadores front que vienen de la maquetación nunca hayan trabajado con este concepto, trataré de explicar también algunos fundamentos básicos, por lo que es posible que esta entrada le resulte un tostón a más de uno. El que avisa no es traidor :P.

Clases

A diferencia de java y php, entre otros, hasta la versión 6, javaScript carecía de clases a pesar de ser un lenguaje orientado a objetos. En su lugar se manejaban prototipos y un tipo de objetos creados mediante función constructora que, todo hay que reconocerlo, hacían que algunas construcciones fueran un poco farragosas. Sin embargo, con ts podemos trabajar con clases (muy similares a las nativas de js), que son mucho más elegantes. Ahora bien, ¿qué diantres es una clase?

Una clase es un conjunto de funciones, llamadas métodos, y de variables, denominadas propiedades, que permite definir instancias con sus mismas características. Por ejemplo, podemos tener una clase «Gato» a partir de la cual podemos crear gatos concretos, unos de pelo blanco, otros atigrados, negros, etcétera.

Las clases se definen mediante la palabra reservada class y para usarlas se crean instancias mediante el operador new.

class MyClass {

myMethod() {

console.log('foo');

}

}

let myInstance = new MyClass();

myInstance.myMethod();

Constructor

En js, como en otros lenguajes, las clases cuentan con un método especial -en este caso denominado constructor- que se invoca de forma automáticamente cuando se saca una instancia, con el cual se inicializan, se setean por primera vez, las propiedades que lo necesiten. Por ejemplo, siguiendo con el manido ejemplo del gato, vamos a preparar una clase Gato y al crear sus instancias, mediante el constructor, vamos a definir el color de su pelo.

class Gato {

pelo: string;

constructor(color: string) {

this.pelo = color;

}

}

/* Este gato es blanco */

let gatoBlanco = new Gato('blanco');

/* Y este otro, negro */

let gatoNegro = new Gato('negro');

Encapsulación

Para evitar problemas en aplicaciones grandes es muy conveniente seguir la llamada Ley de Deméter, que viene a decir que cada pieza debe conocer y exponer a las demás piezas lo mínimo imprescindible para funcionar. Esto se consigue en las clases mediante la encapsulación, esto es, indicando de forma expresa qué métodos y propiedades son accesibles y cuáles no.

Por defecto, de forma implícita, todas las cosas son públicas, accesibles, lo cual se puede escribir de forma explícita anteponiendo el término public delante de métodos y propiedades. En cambio, si queremos que sean privadas, inaccesibles, hay que utilizar la palabra private.

Por ejemplo, imaginemos que necesitamos añadir a un número una cantidad para no sé qué operación numérica. Desde fuera, solo se necesita acceder al método que añade la cantidad, pero no a la cantidad a añadir, la cual permanece inaccesible, protegida, con la declaración private.

class Sum {

private propiedadInaccesible: number = 5;

public metodoAccesible(cantidad: number) {

return cantidad + this.propiedadInaccesible;

}

}

let instanciaSum = new Sum();

instanciaSum.metodoAccesible(3); // 8

instanciaSum.propiedadInaccesible // error

Además, hay un tercer tipo de modificador denominado protected, pero lo vamos a ver después, cuando hable de la herencia.

Getters y setters

Como vemos, la clave de una arquitectura saludable, o al menos una de ellas, es ir jugando con lo que se expone y lo que se oculta de tal manera que tengamos controlados los puntos de acceso y en este sentido también juegan un papel fundamental los denominados métodos getter, que se podrían traducir como accesores o lectores, y setter, definidores o modificadores.

En general, en los lenguajes con clases, mediante el método que se usa de getter se permite leer, y solo leer, las propiedades que se necesiten y mediante el método que se emplee de setter, el modificarlas. Y solo se podrán leer o modificar desde fuera a partir de estos dos métodos. En ts también podemos definir este tipo de métodos mediante los modificadores get y set.

Por ejemplo, volviendo con nuestros gatos, no nos interesa que cualquiera pueda cambiarles el nombre desde un método que vete tú a saber cuál entre las toneladas de código que hay en una aplicación, por lo que podemos añadir a nuestra clase un método set para eso y, además, un método get que nos permita recuperar el nombre cuando lo necesitemos... qué sé yo, para avisarle de que la comida está lista o que deje de arañar el sofá. Observad cómo ts ya sabe de forma automática si estamos invocando al getter o al setter según estemos o no definiendo un valor tras el signo igual.

class Gato {

private pelo: string;

private nombreBicho: string;

constructor(color: string) {

this.pelo = color;

}

get nombre() {

return this.nombreBicho;

}

set nombre(nuevoNombre: string) {

this.nombreBicho = nuevoNombre;

}

}

let gatoBlanco = new Gato('blanco');

gatoBlanco.nombre = 'Monikongui';

if ( gatoBlanco.nombre === 'Monikongui' ) {

console.log('Pues sí, ese es el nombre de mi gata :P');

}

Las clases suelen seguir esta estructura:

  1. Primero se declaran las propiedades
  2. Luego la función constructora
  3. Las funciones que trabajan algo
  4. Las que devuelven algo

Herencia

Otra característica muy potente de las clases es su capacidad de combinarse entre sí en un proceso conocido como herencia. Para comprender su potencial vamos a seguir con los mamíferos, que es un ejemplo muy claro aunque esté repetido hasta la saciedad.  En una clase se definen los datos comunes a todos ellos, como que son de sangre caliente y vertebrado. Luego, para cada tipo específico de mamífero se declara a su vez una clase que hereda las características de la paterna, por lo que no tenemos que repetir en estas últimas los rasgos comunes. Para que se entienda:

Mamíferos

vertebrados: true

tipoSangre: caliente

Gatos hereda de Mamíferos

hábitos: ronronear

color: variado

Pitufos hereda de Mamíferos

hábitos: pitufear

color: azul

De esta manera, tanto los pitufos como los gatos tienen la sangre caliente aunque este rasgo no se haya definido en su clase específica.

Vamos con otro ejemplo, esta vez con código y usando el término para extender clases, que es extend.  Una calculadora realiza distintas operaciones específicas, como sumar, restar o multiplicar, que comparten un mismo procedimiento, mostrar el resultado, por lo que podríamos crearla definiendo una clase para cada tipo de operación y una común de la que hereden las demás con los procedimientos compartidos. Algo así.

class Calculadora {

public mostrarResultado(result: number) {

console.log(result);

}

}

class Sumar extends Calculadora {

public sum(a: number, b: number) {

return a + b;

}

}

let operacion = new Sumar();

let resultado = operacion.sum(2, 3);

operacion.mostrarResultado(resultado);

De esta manera, no solo nos ahorramos tener que incluir en cada operación el método mostrarResultado(), con lo que ahorramos código, sino que además si en un futuro hubiera que modificar este método solo tendríamos que hacerlo en un sitio. Entendido esto, vamos ahora con el super.

Por convención, el nombre de las clases se suele comenzar con mayúscula.

super

En general, el operador this se utiliza dentro de una «entidad» (función, método / clase / objeto) para referirse a los demás miembros de la misma.

const miObjeto = {

metodoUno: function() {

this.metodoDos();

},

metodoDos: function() {

console.log('hey, hey');

}

};

Para conseguir esto mismo con clases, es decir, para referirse a los métodos y propiedades de la clase que es heredada, hay que utilizar el operador super. Así, por ejemplo, en el caso anterior, en lugar de invocar al método de mostrar resultado, podríamos haber hecho algo así.

class Sumar extends Calculadora {

public sum(a: number, b: number) {

super.showResult(a + b);

}

}

Es super «recorre» ascendentemente toda la cadena de herencias.

class A {

public foo() {

console.log('foo');

}

}

class B extends A {

// lo que sea que hace esta clase

}

class C extends B {

public bar() {

super.foo();

}

}

let c = new C();

c.bar(); // foo

Termino este epígrafe indicando que, cuando se maneja super en el método constructor, antes hay que invocarlo.

class Frutas {

public color: string;

public sabor: string;

}

class Fresas extends Frutas {

constructor (col:string, sab:string) {

super();

this.color = col;

this.sabor = sab;

}

}

protected

Ahora que sabemos qué es la herencia podemos retomar el modificador protected que vimos más arriba. Las propiedades y los métodos definidos como protected se comportan de manera muy parecida a los private, pero sí que son accesibles por las clases hijas.

Por ejemplo, si intentáramos acceder a propiedadProtegida nos daría error:

class miClase {

protected propiedadProtegida = 'foo';

}

let inst = new miClase();

inst.propiedadProtegida;

Sin embargo, sí que podríamos acceder desde una clase hija.

class miClase {

protected propiedadProtegida = 'foo';

}

class claseHija extends miClase {

getFoo() {

return super.propiedadProtegida;

}

}

let inst = new claseHija();

inst.getFoo();

static

En otros lenguajes existe un tipo especial de métodos y propiedades denominados estáticos, que se caracterizan porque no se necesita crear una instancia, un objeto, para usarse, sino que pueden emplearse directamente. En ts también podemos definir cosas estáticas con la misma intención, las cuales solo se pueden utilizar invocando directamente a la clase. Es decir, no se pueden manejar desde las instancias.

class ClaseFormidable {

static metodoEstatico() {

console.log('foo');

}

public metodoNormal() {

console.log('bar');

}

}

ClaseFormidable.metodoEstatico(); /* esto chufla */

let instFormidable = new ClaseFormidable();

instFormidable.metodoEstatico(); /* esto casca */

Ahora bien, me parece que utilizar métodos estáticos en javaScript carece de sentido, ya que para este tipo de casos es mucho más elegante y práctico recurrir a un objeto con notación de literales.

clases abstractas

En programación, se habla de «abstracción» para referirse a elementos que definen comportamientos independientemente de su concreción. Por ejemplo, peste algoritmo que sirve para apagar y encender cosas, lo que sea, es un modelo abstracto que se puede emplear, concretar, en una computadora, un teléfono, la luz de una casa... cualquier cosa que se apague y se encienda.

abstracción de interruptor {

if ( estado == 0 ) {

apaga;

} else if ( estado == 1 ) {

enciende;

}

}

La abstracción del interruptor no puede utilizarse directamente, sino que debemos concretarla en cosas, en clases, que sí disponen de un dispositivo para encender y apagar, como el Halcón Milenario.

class HalconMilenario extends interruptor {

saltoHiperespacial() {

implementa la abstracción de interruptor;

}

}

Las clases abstractas, por lo tanto, son muy útiles para definir los comportamientos comunes que deben tener distintas clases hijas de las que sacaremos las instancias y lo bueno es que con typeScript también las tenemos disponibles. Son idénticas a las clases normales, pero hay que anteponer el término abstract antes de declararlas.

abstract class Libros {

serLeido(): void {

console.log('un libro está siendo leído');

}

}

/* Esto casca, ya que no podemos instanciar una clase abstracta */

let donQuijote = new Libros();

class Novelas extends Libros {}

/* Esto funciona */

let donQuijote = new Novelas();

Por último, indicar que dentro de una clase abstracta podemos definir métodos abstractos, que no se implementan en la clase padre, sino en las hijas, lo que sirve para generar un «contrato», pero este tema quedará más claro en la siguiente entrada, cuando veamos qué son los interfaces. Por hoy lo dejo aquí. Ha quedado un poco ladrillo, pero espero que resulte útil si se desconocía qué son las clases.

|| Tags: ,

valoración de los lectores sobre typeScript (3): clases

  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • 4.9 sobre 5 (7 votos)

¿Te ha parecido útil o interesante esta entrada?
dormido, valoración 1 nadapensativo, valoración 2 un poco sonrisa, valoración 3 a medias guiño, valoración 4 bastante aplauso, valoración 5 mucho

Tú opinión es muy importante, gracias por compartirla!

Los comentarios están cerrados.