Introdução à Orientação a Objetos com TypeScript
A Programação Orientada a Objetos (POO) organiza o código em entidades chamadas objetos, que encapsulam dados (propriedades) e comportamentos (métodos). Com TypeScript, esse paradigma é fortalecido com a adição de tipagem estática, interfaces e modificadores de acesso, tornando o código mais robusto e legível.
Classe com Tipagem
Uma classe define a estrutura de um objeto. No TypeScript, utilizamos tipos explícitos para garantir segurança e clareza.
class Animal {
constructor(
public name: string,
public species: string
) {}
describe(): string {
return `${this.name} é um ${this.species}.`;
}
}
Objeto (Instância da Classe)
Um objeto é criado a partir de uma classe usando a
palavra-chave new
. O TypeScript garante que os valores
estejam de acordo com os tipos definidos na classe.
const dog = new Animal('Rex', 'cachorro');
console.log(dog.describe()); // Rex é um cachorro.
Propriedades e Métodos com Acessibilidade
Propriedades armazenam valores, enquanto métodos são
funções associadas à classe. No TypeScript, usamos
public
, private
e readonly
para
controlar o acesso e a modificação desses elementos.
class Calculator {
private history: string[] = [];
public add(a: number, b: number): number {
const result = a + b;
this.log(`${a} + ${b} = ${result}`);
return result;
}
private log(operation: string): void {
this.history.push(operation);
}
public getHistory(): string[] {
return this.history;
}
}
const calc = new Calculator();
console.log(calc.add(5, 3)); // 8
console.log(calc.getHistory()); // ['5 + 3 = 8']
Interfaces
Interfaces definem a estrutura que uma classe deve seguir. Elas não geram código no JavaScript final, mas ajudam a garantir que certas propriedades e métodos estejam presentes.
interface Describable {
name: string;
describe(): string;
}
class Person implements Describable {
constructor(public name: string, public age: number) {}
describe(): string {
return `${this.name} tem ${this.age} anos.`;
}
}
const user: Describable = new Person('Ana', 28);
console.log(user.describe()); // Ana tem 28 anos.
Classes Genéricas
Classes que aceitam tipos genéricos para seus membros, métodos ou propriedades.
Exemplo 1: Classe genérica que armazena um valor
class Storage<T> {
private _value: T;
constructor(value: T) {
this._value = value;
}
getValue(): T {
return this._value;
}
}
Exemplo 2: Classe genérica que representa um par de valores
class Pair<T, U> {
constructor(public first: T, public second: U) {}
}
Exemplo 3: Classe genérica que representa uma pilha (stack)
class Stack<T> {
private items: T[] = [];
push(item: T) {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
Interfaces Genéricas
Interfaces que definem contratos genéricos, flexíveis para diferentes tipos.
Exemplo 1: Interface genérica para uma função que recebe e retorna o mesmo tipo
interface IdentityFn<T> {
(arg: T): T;
}
Exemplo 2: Interface genérica para um objeto com propriedade data
interface Container<T> {
data: T;
}
Exemplo 3: Interface genérica para uma estrutura que pode armazenar múltiplos tipos
class Stack<T> {
interface KeyValuePair<K, V> {
key: K;
value: V;
}}
Restrições nos Genéricos
Restrições limitam os tipos genéricos para garantir que eles possuam certas propriedades ou métodos.
Exemplo 1: Restringindo T para tipos que possuem length
function loggingIdentity<T extends { length: number }>(arg: T): T {
console.log(arg.length);
return arg;
}}
Exemplo 2: Interface para restrição de tipo que deve possuir método toString
interface Stringifiable {
toString(): string;
}
function printString<T extends Stringifiable>(arg: T): void {
console.log(arg.toString());
}
Exemplo 3: Classe genérica com restrição para tipos que têm método compareTo
interface Comparable {
compareTo(other: this): number;
}
class SortedCollection<T extends Comparable> {
private items: T[] = [];
add(item: T) {
this.items.push(item);
this.items.sort((a, b) => a.compareTo(b));
}
}