Ejemplo de clases provistas por JS
// Clases provistas por JS
const date = new Date();
// Usando métodos de la clase
date.getHours();
date.getTime();
date.toISOString();
// Creando una instanciona usando argumentos
const date2 = new Date(1993, 1, 12); // 0 enero 11 dic
Creando tus clases
export class MyDate {
// Atributos públicos
year: number;
month: number;
day: number;
// Atributos privados
private counter: number;
// Inicializando valores desde el constructor
constructor(year: number, month: number, day: number) {
this.year = year;
this.month = month;
this.day = day;
}
// Método público
printFormat(): string {
const day = this.addPadding(this.day);
const month = this.addPadding(this.month);
return `${day}/${month}/${this.year}`;
}
// Método público que nos permite agregar tiempo
add(amount: number, type: 'days' | 'months' | 'years') {
if (type === 'days') {
this.day += amount;
}
if (type === 'months') {
this.month += amount;
}
if (type === 'years') {
this.year += amount;
}
}
// Un atributo private no puede ser leido ni modificado desde
// el exterior, pero a veces necesitamos que se pueda leer y no modificar
// para este caso podemo crear un método publico que nos permita exponer
// el valor de la variable privada counter
getCounter() {
return this.counter;
}
// Método Privado
private addPadding(value: number) {
if (value < 10) {
return `0${value}`;
}
return `${value}`;
}
}
const myDate = new MyDate(2021, 3, 13);
console.log(myDate);
console.log(myDate.printFormat());
myDate.add(3, 'days');
Costructor y atributos de forma abreviada
Podemos definir los atributos de la clase al mismo tiempo que definimos el constructor
// Forma Original
export class MyDate {
// Atributos públicos
year: number;
month: number;
private day: number;
// Atributos privados
private counter: number;
// Inicializando valores desde el constructor
constructor(year: number, month: number, day: number) {
this.year = year;
this.month = month;
this.day = day;
}
}
// Forma Abreviada
export class MyDate {
constructor(
// se debe indicar public o private siempre
public year: number = 1993,
public month: number = 7,
private day: number = 9
) {}
}
Getters
Nos permiten obtener datos, cuando solo necesitamos "leer" los valores de los atributos, no es necesario definir getters para cada atributo, pero en algunos casos se puede requerir realizar algunas acciones antes de retornar el valor, y es ahi donde los getters cobran especial importancia.
export class MyDate {
//... class definition
// Definiendo el getter
get day() {
// code
return this._day;
}
// Definiendo un método
get isLeapYear(): boolean {
if (this.year % 400 === 0) return true;
if (this.year % 100 === 0) return false;
return this.year % 4 === 0;
}
}
// Usamos nuestros getters
const myDate = new MyDate(2001, 7, 10);
console.log(myDate.day);
console.log(myDate.isLeapYear);
Setters
No permite definir valores de los atributos, cuando el proceso de setear esos valores es solo la asignación, no se justifica el uso de setters, pero al igual que los getters, a veces se requieres implementar algunas condiciones extras antes de almacenar el valor (como el ejemplo de set month mas abajo) y ahi los setters nos ayudan.
export class MyDate {
//... class definition
// Getter del atributo
get month() {
return this._month;
}
// Setter del atributo
set month(newValue: number) {
if (newValue >= 1 && newValue <= 12) {
this._month = newValue;
} else {
throw new Error('month out of range');
}
}
}
const myDate = new MyDate(1993, 7, 10);
console.log(myDate.printFormat());
myDate.month = 4;
console.log('run', myDate.month);
myDate.month = 78;
console.log('esto no debe aparecer', myDate.month);
Herencia
// Definicmos clase base
export class Animal {
constructor(public name: string) {}
move() {
console.log('Moving along!');
}
greeting() {
return `Hello, I'm ${this.name}`;
}
}
export class Dog extends Animal {
constructor(
// Recibes el atributo de la clase padre
name: string,
// Defines nuevos atributos propios de esta clase
public owner: string
) {
// Le pasamos los atributos al constructor del padre (heredado)
super(name);
}
// Definimos un método exclusivo de esta clase
woof(times: number): void {
for (let index = 0; index < times; index++) {
console.log('woof!');
}
}
}
const fifi = new Animal('fifi');
fifi.move();
console.log(fifi.greeting());
const cheis = new Dog('cheis', 'nico');
cheis.move();
console.log(cheis.greeting());
cheis.woof(5);
console.log(cheis.owner);
Atributos y métodos protegidos
Hasta el momento tenemos métodos public
y private
que nos permite definir el acceso de un atributo o método a nivel de clase y un método o atributo private
no puede ser usado desde sus clases hijas, pero en el caso que se requiera tener un método o atributo que solo pueda ser leído o modificado de forma interna (tal como los privados) pero tanto por su clase padre como sus clases hijas, podemos definirlos como protected
.
export class Animal {
// Definido como protegido para que sea leido por sus hijos
constructor(protected name: string) {}
move() {
console.log('Moving along!');
}
greeting() {
// Podemos acceder a los atributos protected
return `Hello, I'm ${this.name}`;
}
// Podemos crear un método que se pueda acceder de forma interna por los hijos
protected doSomething() {
console.log('dooo');
}
}
export class Dog extends Animal {
constructor(
name: string,
public owner: string
) {
super(name);
}
woof(times: number): void {
for (let index = 0; index < times; index++) {
// el name es atributo protected del padre y lo podemos usar en el hijo
// Pero no fuera de la instancia
console.log(`woof! ${this.name}`);
}
// El método lo podemos utilizar aca dado que es protected
// pero no puede ser usado desde afuera de la instancia
this.doSomething();
}
// Aca podemos sobre escribir un método del padre
move() {
// code
console.log('moving as a dog');
// Si sólo queremos agregar funcionalidad y además mantener
// el comportamiento definido por el padre, basta con usar el super
super.move();
}
}
const cheis = new Dog('cheis', 'nico');
// no podemos usar name desde afura, por que es protected
// cheis.name = 'otro nombre';
cheis.woof(1);
// cheis.doSomething();
cheis.move();
Atributos y métodos estáticos
En algunos casos queremos que nuestras clases tengan atributos o métodos que se puedan utilizar sin la necesidad de crear una instancia de la clase, a estos atributos y métodos se les define como static
, en otros lenguajes se les llama tambien metodos o atributos de clase.
class MyMath {
static readonly PI = 3.14;
static max(...numbers: number[]) {
return numbers.reduce((max, item) => max >= item ? max: item, 0);
}
}
// No es necesario crear una instanciam bastara con llamar directamente a la clase
// para hacer uso de los métodos estaticos.
console.log('MyMath.PI', MyMath.PI);
console.log('MyMath.max', MyMath.max(12,2,1,1112,9));
const numbers = [12,2,1,1112,9, 3000];
console.log('MyMath.max', MyMath.max(...numbers));
console.log('MyMath.max', MyMath.max(-1, -9, -8));
Implementación de interfaces
En POO las Interfaces son usadas para indicar qué métodos debe obligatoriamente implementar (contener) una Clase (aunque no tienen por qué comportarse del mismo modo).
JavaScript no admite interfaces, por lo que, como desarrollador de JavaScript, podría tener o no experiencia con ellas. En TypeScript, puede usar interfaces igual que en la programación tradicional orientada a objetos. (ver mas)
...una interfaz se utiliza para establecer un "contrato de código" que describa las propiedades requeridas de un objeto y sus tipos. Por lo tanto, se puede utilizar una interfaz para asegurar la forma de la instancia de la clase. Las declaraciones de clase pueden hacer referencia a una o varias interfaces en su cláusula
implements
para validar que proporcionan una implementación de las interfaces. (ver mas)
Importante mencionar que las interfaces:
- No tienen constructores
- No implementan lógica, son métodos abstractos
- Los atributos y métodos definidos en una interfaces serán de tipo publico en la implementación
- Para hacer uso de ella utilizamos la palabra reservada
implements
// Definimos nuestra interface
export interface InterfaceDriver {
// Definimos atributos (serán públicos)
database: string;
password: string;
port: number;
// Definimos métodos (públicos)
connect(): void;
disconnect(): void;
isConnected(name: string): boolean;
}
// Definimos nuestra 1era clase que IMPLEMENTA la interface
class PostgresDriver implements InterfaceDriver{
constructor(
public database: string,
public password: string,
public port: number,
// Podemos agregar atributos adionales a la clase
private host: number,
) {}
// Implementamos (obligatoriamente) los métodos que define nuestra interface
// Recuerde que la lógica de cada método se define en la implementación, la
// interface sólo define los nombres de los métodos.
disconnect(): void {
// code
}
isConnected(name: string): boolean {
return true;
}
connect(): void {
// code
}
}
// Definimos nuestra 1era clase que IMPLEMENTA la interface
class OracleDriver implements InterfaceDriver{
constructor(
public database: string,
public password: string,
public port: number
) {}
connect(): void {
// code
}
disconnect(): void {
// code
}
isConnected(name: string): boolean {
return true;
}
}
Clases abstractas
En algunas situaciones podemos tener multiples clases que comparten comportamientos (métodos con lógica de negocio) y atributos, una forma de agrupar dichos métodos y propiedades es utilizando clases abstractas.
Las clases abstractas no pueden ser instanciadas, debe ser utilizada mediante la herencia (extends
) ( no podemos hacer un new ) por lo cual se parecen un poco a las interfaces.
Pueden definir métodos abstractos, sin lógica en ellos y obligando a las clases hijas a implementar el método y definir su comportamiento.
//animal.ts
export abstract class Animal{
// Definimos nuestro constructor
// usamos _ para definir variable local y que no se confunda con
// las implementaciones
constructor(protected _nombre:string){}
// Indicamos que es necesario implementar este método en los hijos
abstract desplazar(): void;
// Definimos un método
get nombre(): string{
this._nombre=nombre;
}
set nombre(nombre:string){
this._nombre=nombre;
}
}
//gato.ts
import {Animal} from "./animal";
// Heredamos de Animal usando extends
export class Gato extends Animal{
// Definimos el constructor
constructor(nombre: string, private raza:string){
// Invocamos al constructor del padre
super(nombre);
}
// Definimos el método abstracto de indicado por nuestra clase padre
desplazar():void {
console.log(`${this.nombre} camina sigilosamente`);
}
// Implementamos un método que sólo es de nuestra clase hija
ronronear():void{
console.log(`${this.nombre} ronronea`);
}
}