Bienvenidos sean a este post, hoy hablaremos sobre uno de los pilares de OOP.
La clases son las definiciones de un objeto, tambien se las considera como moldes, y estas definen que tipos de datos contendran y cuales son las acciones que podra efectuar. Tanto las interfaces como las clases son la piedra angular de OOP, Programacion Orientada a Objetos, veamos una clase simple:
class clase {
id: number;
print(): void {
console.log("Se llamo al metodo print()");
}
}
let objeto = new clase();
objeto.print();
Esta es una clase muy simple con una propiedad (variable) y un metodo (funcion) llamado print, la establecemos con salida void para poder manejarlo y este solo mostrara un mensaje de salida. Luego definimos un objeto derivado de esta clase y llamamos al metodo print. Compilemos y veamos la salida:
tinchicus@dbn001vrt:~/lenguajes/ts/21$ tsc
clase.ts:2:2 - error TS2564: Property 'id' has no initializer and is not definitely assigned in the constructor.
2 id: number;
~~
Found 1 error in clase.ts:2
tinchicus@dbn001vrt:~/lenguajes/ts/21$
Si observan el error nos informa que la propiedad id no fue iniciada mediante un constructor, en este caso es new. Tenemos dos posibilidades; definimos un constructor para iniciar a la propiedad o utilizamos una mecanica vista anteriormente. Tomemos la siguiente linea del codigo anterior:
id: number;
Y vamos a modificarlo de la siguiente manera:
id: number | undefined;
En este caso aplicamos la union de tipos que vimos en casos anteriores, por lo tanto al momento de definir un objeto tomara el valor inicial de id y al coincidir nos permitira trabajar. Compilemos y veamos como es su salida:
$ node clase.js
Se llamo al metodo print()
$
Tomemos el ejemplo anterior y hagamos la siguiente modificacion:
class clase {
id: number | undefined;
print(): void {
console.log("id: " + this.id);
}
}
let objeto1 = new clase();
let objeto2 = new clase();
objeto1.id = 1;
objeto2.id = 2;
objeto1.print();
objeto2.print();
La primer diferencia esta en el metodo print donde ahora le pasamos que nos muestre el valor de id pero como nosotros necesitamos que sea del objeto creado y no de la clase (usualmente) le anteponemos el this para indicarle al lenguaje que debe mostrar este dato. Despues creamos dos objetos de la clase anterior, luego estableceremos el valor de id para cada objeto. Lo siguiente sera el llamado del metodo print para cada objeto. Veamos como es la salida:
$ node clase.js
id: 1
id: 2
$
Como pueden ver funciono perfectamente porque devolvio el valor de cada id y this, al igual que en javascript y otros lenguajes, esta para indicarle que debe buscar los datos del segmento de memoria correspondiente a ese objeto y no del otro o la clase.
En el post anterior hablamos sobre las interfaces y estas estan estrechamente relacionadas con las clases. Ahi mencionamos que describe un tipo personalizado y puede contener propiedades y metodos. Aqui mencionamos que una clase es la definicion de un objeto que tambien incluye propiedades y metodos. Esto nos permite utilizar interfaces para describir alguna conducta en comun dentro de un conjunto de clases. Analicemos el siguiente codigo:
interface IHablar {
hablar(): string;
}
function animal_habla(a: IHablar) {
return a.hablar();
}
class Perro implements IHablar {
hablar(): string {
return "Guau!"
};
}
class Gato implements IHablar {
hablar(): string {
return "Miau!"
};
}
let arturo = new Perro();
let invi = new Gato();
console.log("Arturo dice " + animal_habla(arturo));
console.log("Invi dice " + animal_habla(invi));
Lo primero que haremos sera declarar nuestra intterfaz llamada IHablar donde solo declaramos una funcion llamada hablar y devolvera un tipo string. Lo siguiente es establecer una funcion donde recibira objetos del tipo de la interfaz anterior y este devolvera el resultado del metodo hablar del objeto recibido. Luego tenemos dos clases que son iguales, una para representar a un perro y otra para un gato. Ambas poseen un metodo llamada hablar y los dos devuelven un valor de tipo string pero cada uno con su valor. Por ultimo estas dos clases implementan a la interfaz, es decir que se encargan de definir la funcion declarada en esta. Con esto ya tenemos nuestras clases y a su vez la implementacionn de la interfaz en ambas, nuestro siguiente paso sera crear dos objetos de cada una de estas clases. Con nuestros objetos creados mostraremos en la consola un mensaje indicando que recibimos de llamar al metodo animal_habla de cada objeto. Compilemos y veamos como es su salida:
$ node clase.js
Arturo dice Guau!
Invi dice Miau!
$
Al principio de este post mencionamos que una solucion para ese error era mediante un constructor. Los constructores al igual que en otros lenguajes se utiliza para iniciar valores de una clase al momento de crear un objeto. Analicemos el siguiente codigo:
class clase {
id: number;
constructor(_id: number) {
this.id = _id;
}
print(): void {
console.log("clase.id: " + this.id);
}
}
let objeto = new clase(10);
objeto.print();
Esta es similar a la que vimos primero pero con unas sutiles diferencias. La primera es el agregado del constructor, donde recibiremos un valor de tipo number y lo asignaremos al id del objeto mediante this. Despues hicimos una pequeña modificacion donde ahora print mostrara el valor de id pero del objeto (mas alla del mensaje que indica la clase) para finalmente crear el objeto. Pero como ahora tenemos el constructor lo podemos pasar como dato y este se encargara de asignarlo para luego llamar al metodo print del objeto. Con esto comentado veamos como es la salida:
$ node clase.js
clase.id: 10
$
En el caso del constructor usamos como argumento a _id pero solo para diferenciarlo de su propiedad. Tranquilamente podriamos haber usado id como argumento porque typescript tiene scope y por lo tanto diferencia correctamente entre valores locales y globales.
Una caracteristica muy interesante que nos agrega typescript son los modificadores de acceso (public, private y protected) para permitir o restringuir el acceso a las propiedades o metodos de una clase desde el exterior, como si fuera de otros lenguajes mas convencionales. Tomemos el codigo anterior y hagamos la siguiente modificacion:
class clase {
public id: number;
constructor(_id: number) {
this.id = _id;
}
print(): void {
console.log("clase.id: " + this.id);
}
}
let objeto = new clase(10);
console.log("Objeto.id: " + objeto.id);
Es el mismo codigo que hablamos anteriormente pero con dos sutiles diferencias.. La primera es que establecimos como public a la propiedad id y la otra fue quitar el llamado de la funcion print para mostrar directamente el valor de id. Compilemos y veamos como es su salida:
$ node clase.js
Objeto.id: 10
$
En este caso podemos obtener el valor de id establecido mediante el constructor. Hasta aca todo perfecto pero tomemos la siguiente linea:
public id: number;
Y modifiquemosla de la siguiente manera:
private id: number;
La propiedad que era publica ahora la pasamos a privada, compilemos y veamos que sucede:
$ tsc
clase.ts:12:36 - error TS2341: Property 'id' is private and only accessible within class 'clase'.
12 console.log("Objeto.id: " + objeto.id);
~~
Found 1 error in clase.ts:12
$
Como en cualquier lenguaje OOP cuando establecemos un elemento de una clase como privada este queda automaticamente restringuido a la clase y solo puede ser accedida en la misma. Volvamos al codigo y busquemos la siguuiente linea:
console.log("Objeto.id: " + objeto.id);
Y la reemplazaremos por la siguiente:
objeto.print();
Volvemos al codigo original donde en lugar de trabajar con la propiedad lo hacemos mediante el metodo, compilemos y veamos que sucede:
$ node clase.js
clase.id: 10
$
Volvio a funcionar gracias al metodo porque al no ser privado podemos acceder a este y como este se encuentra dentro de la clase si puede acceder a la propiedad para poder obtener el dato, es decir que nosotros podemos restringuir el acceso pero mediante otra forma podemos acceder y/o modificarlo. Hay un tema relacionado a esto pero de eso hablaremos en un momento.
Nota:
Las propiedades y metodos de manera predeterminada son public sino lo informamos.
Una particularidad que se agrego con ECMAScript son los campos privados, si disponemos de un node v12 o superior lo tendremos a nuestra disposicion. Este en lugar de usar la palabra private como vimos anteriormente se reemplaza por el simbolo de numeral (#). Tomemos el codigo anterior y modifiquemoslo de la siguiente manera:
class clase {
#id: number;
constructor(_id: number) {
this.#id = _id;
}
print(): void {
console.log("clase.id: " + this.#id);
}
}
let objeto = new clase(10);
console.log(objeto.#id);
En este caso le agregamos el numeral a todos los id, tanto dentro de la clase como fuera, y nuevamente eliminamos el llamado a print para reemplazarlo por una linea para mostrar el valor de #id en objeto. Compilemos y veamos que sucede:
$ tsc
clase.ts:13:20 - error TS18013: Property '#id' is not accessible outside class 'clase' because it has a private identifier.
13 console.log(objeto.#id);
~~~
Found 1 error in clase.ts:13
$
Como dijimos lo considera como privado y por lo tanto no podemos acceder por fuera de la clase. Como todo tiene sus pros y contras, la primera que es un poco mas practico y se escribe menos pero nos obliga a ser mas cuidadoso y no olvidar de agregar el numeral para cada campo donde este pero creo que su contra mas importante es que podemos perder una mejor visualizacion de cuales son elementos privados o publicos y pensando especialmente en una depuracion de nuestro codigo. Como siempre digo, esto queda al criterio de cada uno y sus respectivas necesidades.
Otra particularidad que tenemos con los constructores es que nos permiten establecer propiedades. Analicemos el sigueinte codigo:
class clase {
constructor(public id: number, private nombre: string) {
this.id = id;
this.nombre = nombre;
}
print(): void {
console.log("objeto.id: " + this.id);
console.log("objeto.nombre: " + this.nombre);
}
}
let objeto = new clase(10, "tinchicus");
console.log(objeto.id);
console.log(objeto.nombre);
En este codigo usaremos el constructor para establecer las dos propiedades de nuestra clase, este nos permite crearlas no solamente iniciarlas como en otros lenguajes, y a su vez tambien podemos establecer cual sera su nivel de acceso, despues en el bloque le estableceremos los valores recibidos, mantenemos el metodo print pero en esta ocasion nos mostrara los dos valores almacenados. Lo primero que haremos sera crear nuestro objeto de esta clase y le pasamos los dos datos para luego mostrar cada valor, veamos que sucede:
$ tsc
clase.ts:14:20 - error TS2341: Property 'nombre' is private and only accessible within class 'clase'.
14 console.log(objeto.nombre);
~~~~~~
Found 1 error in clase.ts:14
$
Como indicamos al crear estas propiedades podemos establecer sus modificadores y como lo pasamos como privado no podremos acceder desde afuera pero si eliminamoss las ultimas dos lineas y la reemplazamos por la siguiente:
objeto.print();
En este caso utilizamos al metodo interno de la clase, compilemos y veamos como es su salida:
$ node clase.js
objeto.id: 10
objeto.nombre: tinchicus
$
Como esta dentro de la misma clase funciono perfectamente y como podemos observar el constructor tambien, a pesar de ser privada la propiedad, porque pudimos establecer todos los valores. Tambien podemos establecer al constructor como privado pero eso es para otra utilidad que no veremos hoy pero sepan que tambien existe.
Ya hablamos que tenemos los modificadores de acceso (public, private y protected) pero no mencionamos a uno muy parrticular como es readonly. Tal como su nombre lo indica es solo lectura. Si hacemos una comparacion este equivale a const donde una vez establecido el valor no se puede volver a modificar. Analicemos el siguiente codigo:
class clase {
constructor(public id: number, readonly nombre: string) {
this.id = id;
this.nombre = nombre;
}
public cambiarNombre(n: string) {
this.nombre = n;
}
print(): void {
console.log("objeto.id: " + this.id);
console.log("objeto.nombre: " + this.nombre);
}
}
let objeto = new clase(10, "tinchicus");
objeto.print();
Este es el codigo anterior con unas pequeñas modificaciones, la primera es que pasamos a nombre de private a readonly y la segunda es el metodo cambiarNombre. En esta recibiremos un nuevo valor para asignarselo a la propiedad con el mismo nombre, cuack, y el resto del codigo sigue siendo igual al ultimo que vimos. Compilemos y veamos que sucede:
$ tsc
clase.ts:7:8 - error TS2540: Cannot assign to 'nombre' because it is a read-only property.
7 this.nombre = n;
~~~~~~
Found 1 error in clase.ts:7
$
Como pueden ver nos notifica que el campo nombre esta establecido como solo-lectura. Por lo tanto, no podemos modificarlo con nuestro nuevo metodo. Y sin necesidad de utilizarlo porque no lo llamamos en ningun momento. Antes de cambiar de tema les comento que este modificador al igual que los otros pueden ser usados en las interfaces.
Una caracteristica que nos agrego ECMAScript 5 son los accesores de propiedades, mas conocidos como Getter y Setter. Esto es basicamente un metodo que es llamado cuando intentamos conseguir un valor (get) o establecer un valor (set). Para entender el concepto vamos a analizar el siguiente codigo:
class clase {
private _id: number = 0;
get id(): number {
console.log("get id");
return this._id;
}
set id(v: number) {
console.log("set id");
this._id = v;
}
print(): void {
console.log("objeto.id: " + this._id);
}
}
let objeto = new clase();
objeto.id = 10;
console.log(objeto.id);
En nuestra clase primero definimos una propiedad _id que sera privada. Lo siguiente es usar get para un metodo llamado id donde mostraremos una notificacion de la accion y lo siguiente es devolver el valor que posee la propiedad _id. Lo siguiente es un set para un metodo id donde al igual que en el anterior mostraremos una notificacion y en este caso le estableceremos el valor recibido a _id. Seguimos con el metodo print para mostrar el valor de _id. Por fuera de la clase primero crearemos un nuevo objeto con la clase. Lo siguiente sera establecer el valor de _id pero mediante id y luego lo mostraremos en pantalla. Compilemos y veamos como es su salida:
$ node clase.js
set id
get id
10
$
Observen que nos notifico cuando establecemos el valor mediante id (set) y luego nos notifico que buscamos el valor de id (get) y finalmente mostro el valor. Observen como de una manera simple pudimos establecer y recuperar el valor de una propiedad mediante otra propiedad pero en realidad es a traves de get y set.
Otra palabra que podemos usar es static para un metodo y al igual que en otros lenguajes significa que existira una sola instancia de dicho metodo en todo el codigo por lo tanto no es necesario crear un objeto para poder utilizarlo. Analicemos el siguiente codigo:
class clase {
id: number = 1;
static print(): void {
console.log("llamado a print()");
};
}
clase.print();
Volvimos a la clase simple, primero definimos a la propiedad id con un valor. Lo siguiente es establecer a print como static pero ahora solo mostrara un mensaje. Para finalmente mediante la clase llamar a print. Veamos como es su salida:
$ node clase.js
llamado a print()
$
Esto es el concepto de static para un metodo en una clase pasemos a ver lo mismo pero para las propiedades y para ello tomemos el codigo anterior y realicemos la siguiente modificacion:
class clase {
static id: number = 1;
incrementar() {
clase.id++;
};
}
let objeto1 = new clase();
let objeto2 = new clase();
console.log("clase.id: " + clase.id);
objeto1.incrementar();
console.log("clase.id: " + clase.id);
objeto2.incrementar();
console.log("clase.id: " + clase.id);
En este caso establecimos como static a id, con el mismo valor que tenia en el ejemplo anterior, pero quitamos al metodo para crear uno nuevo llamado incrementar donde incrementaremos a id, en este caso le pasamos la clase precedentemente porque recuerden que al ser static se debe indicaar desde donde procede.. Lo siguiente sera crear dos objetos de la clase anterior. Para luego mostrar el valor de id, llamamos a la funcion desde objeto1, volvemos a mostrar el valor de id, volvemos a incrementarlo pero ahora desde el objeto2 y finalmente mostramos nuevamente a id. Compilemos y veamos como es su salida:
$ node clase.js
clase.id: 1
clase.id: 2
clase.id: 3
$
Como mencionamos en el caso de los metodos, al ser static existe una sola instancia en todo el codigo por lo tanto debe ser usada la de la clase y sin impotar desde donde llamemos al metodo incrementar este afectara siempre a la misma propiedad, un ejemplo muy util es como contador para ver cuantas veces pasamos por un determinado lugar o de otro tipo. Bajo ciertas circunstancias es mejor que tener una variable global que trabaja en todo el codigo porque son mas rapidas y consumen menos memoria.
El ultimo tema que hablaremos de clases son los namespaces. Al igual que en otros lenguajes nos sirven principalmente para poder diferenciar propiedades o metodos con el mismo nombre dentro de un proyecto grande. Analicemos el siguiente codigo:
namespace primero {
export class clase {
id: number = 1;
print(): void {
console.log("Llamado desde namespace primero");
};
}
}
namespace segundo {
export class clase {
id: number = 1;
print(): void {
console.log("Llamado desde namespace segundo");
};
}
}
let first = new primero.clase();
let second = new segundo.clase();
first.print();
second.print();
Este es un ejemplo de una situacion que nos puede suceder en un codigo muy largo. Donde tenemos dos clases que deben llamarse iguales pero haran distintas acciones, en este caso solo muestran un mensaje distinto pero el resto coinciden. Para evitar tener inconvenientes se las envuelve mediante namespace y su bloque y a este lo identificamos con un nombre unico, este no se puede volver a usar en todo el codigo, para ubicarlo correctamente al momento de crear un objeto. Si observan las estructuras son iguales y agregamos la palabra export para las clases porque de lo contrario no podran ser accedidas desde afuera, el resto sigue como lo vimos hasta ahora. Lo siguiente es la creacion de dos objetos en base a estas clases, tomemos una de ejemplo:
let first = new primero.clase();
A la hora de definirla es similar a lo visto anteriormente pero ahora debemos agregarle el namespace para poder establecer su ubicacion correcta. Con nuestros objetos creados llamamos al metodo print para ambos casos. Compilemos y veamos que sucede:
$ node clase.js
Llamado desde namespace primero
Llamado desde namespace segundo
$
De esta forma pudimos llamar a los metodos de cada namespace, como dijimos anteriormente esta especialmente enfocado para cuando tengamos casos donde se superpongan nombres y debamos diferenciarlos. Con esto hemos cubierto clases y typescript, y ver como mediante este lenguaje pudimos incoporar mucho del OOP.
En resumen, hoy hemos visto clases, que es, para que sirve, como se utiliza, caracteristicas y conductas de otros lenguajes en Javascript, una serie de ejemplos para verlos en accion, espero les haya sido de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.


Donación
Es para mantenimento del sitio, gracias!
$1.50
