Metodi Classi

I metodi delle classi in TypeScript sono funzioni definite all’interno di una classe che operano sui dati dell’istanza o della classe stessa. I metodi possono essere di istanza (operano su oggetti specifici), statici (operano sulla classe), privati, protetti o pubblici, e possono usare this per accedere a proprietà e altri metodi, incapsulando comportamenti e logica all’interno della struttura della classe.
Metodi di Istanza
Metodi che operano su istanze specifiche della classe.
class Calcolatrice {
// Metodo base
somma(a: number, b: number): number {
return a + b;
}
// Metodo con this
risultato: number = 0;
aggiungi(valore: number): void {
this.risultato += valore;
}
getRisultato(): number {
return this.risultato;
}
// Metodo che chiama altro metodo
raddoppia(): void {
this.aggiungi(this.risultato);
}
}
const calc = new Calcolatrice();
console.log(calc.somma(5, 3)); // 8
calc.aggiungi(10);
console.log(calc.getRisultato()); // 10
Modificatori di Accesso
Controllare visibilità dei metodi con public, private, protected.
class BankAccount {
private saldo: number = 0;
// Public (default)
public deposita(importo: number): void {
this.saldo += importo;
}
// Private: solo interno alla classe
private validaImporto(importo: number): boolean {
return importo > 0;
}
public preleva(importo: number): boolean {
if (this.validaImporto(importo) && importo <= this.saldo) {
this.saldo -= importo;
return true;
}
return false;
}
// Protected: accessibile in sottoclassi
protected getSaldo(): number {
return this.saldo;
}
}
class SavingsAccount extends BankAccount {
calcolaInteressi(): number {
// Può accedere a protected
return this.getSaldo() * 0.05;
}
}
const account = new BankAccount();
account.deposita(100);
// account.getSaldo(); // ERRORE: protected
Metodi Statici
Metodi che appartengono alla classe, non alle istanze.
class MathUtils {
// Metodo statico
static PI = 3.14159;
static areaCircolo(raggio: number): number {
return this.PI * raggio ** 2;
}
static max(a: number, b: number): number {
return a > b ? a : b;
}
// Metodo statico che usa altro statico
static volumeSfera(raggio: number): number {
return (4 / 3) * this.PI * raggio ** 3;
}
}
// Chiamata senza istanza
console.log(MathUtils.areaCircolo(5));
console.log(MathUtils.max(10, 20));
// Non accessibile da istanza
// const utils = new MathUtils();
// utils.areaCircolo(5); // ERRORE
Arrow Functions come Metodi
Arrow functions catturano this lessicalmente, utile per callbacks.
class Timer {
secondi = 0;
// Metodo normale: this dinamico
incrementaNormale() {
this.secondi++;
}
// Arrow function: this lessicale
incrementaArrow = () => {
this.secondi++;
};
avvia() {
// Metodo normale perde this
// setInterval(this.incrementaNormale, 1000); // this undefined
// Arrow function mantiene this
setInterval(this.incrementaArrow, 1000); // OK
// Alternativa: bind
setInterval(this.incrementaNormale.bind(this), 1000); // OK
}
}
// Event handlers
class Button {
conteggio = 0;
// Arrow mantiene this
onClick = () => {
this.conteggio++;
console.log(`Cliccato ${this.conteggio} volte`);
};
}
const btn = new Button();
document.addEventListener("click", btn.onClick); // this corretto
Getter e Setter
Metodi accessor per proprietà con logica custom.
class Persona {
private _nome: string = "";
private _cognome: string = "";
// Getter
get nomeCompleto(): string {
return `${this._nome} ${this._cognome}`;
}
// Setter
set nomeCompleto(valore: string) {
const parti = valore.split(" ");
this._nome = parti[0];
this._cognome = parti[1] || "";
}
// Getter con validazione
private _eta: number = 0;
get eta(): number {
return this._eta;
}
set eta(valore: number) {
if (valore < 0) {
throw new Error("Età non valida");
}
this._eta = valore;
}
}
const persona = new Persona();
persona.nomeCompleto = "Mario Rossi";
console.log(persona.nomeCompleto); // "Mario Rossi"
persona.eta = 30;
console.log(persona.eta); // 30
Metodi Asincroni
Metodi che restituiscono Promise per operazioni asincrone.
class ApiService {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
// Metodo async
async fetchUtente(id: string): Promise<User> {
const response = await fetch(`${this.baseUrl}/users/${id}`);
return await response.json();
}
// Con gestione errori
async salvaUtente(utente: User): Promise<void> {
try {
await fetch(`${this.baseUrl}/users`, {
method: "POST",
body: JSON.stringify(utente),
});
} catch (error) {
console.error("Salvataggio fallito:", error);
throw error;
}
}
// Multiple await
async caricaDati(userId: string): Promise<UserData> {
const utente = await this.fetchUtente(userId);
const posts = await this.fetchPosts(userId);
return { utente, posts };
}
private async fetchPosts(userId: string): Promise<Post[]> {
const response = await fetch(`${this.baseUrl}/posts?user=${userId}`);
return await response.json();
}
}
Overloading di Metodi
Dichiarare multiple signature per un metodo.
class Formatter {
// Signature overload
format(valore: string): string;
format(valore: number): string;
format(valore: boolean): string;
// Implementazione
format(valore: string | number | boolean): string {
if (typeof valore === "string") {
return valore.toUpperCase();
}
if (typeof valore === "number") {
return valore.toFixed(2);
}
return valore ? "Vero" : "Falso";
}
}
const formatter = new Formatter();
console.log(formatter.format("hello")); // "HELLO"
console.log(formatter.format(42.567)); // "42.57"
console.log(formatter.format(true)); // "Vero"
Metodi con Generics
Metodi parametrizzati con type parameters per flessibilità.
class Collezione<T> {
private items: T[] = [];
aggiungi(item: T): void {
this.items.push(item);
}
// Metodo generic aggiuntivo
mappa<U>(fn: (item: T) => U): U[] {
return this.items.map(fn);
}
trova(predicate: (item: T) => boolean): T | undefined {
return this.items.find(predicate);
}
// Generic con constraint
filtra<U extends T>(predicate: (item: T) => item is U): U[] {
return this.items.filter(predicate);
}
}
const numeri = new Collezione<number>();
numeri.aggiungi(1);
numeri.aggiungi(2);
numeri.aggiungi(3);
const stringhe = numeri.mappa((n) => n.toString()); // string[]
Method Chaining
Restituire this per concatenazione di chiamate.
class QueryBuilder {
private query: string = "";
private conditions: string[] = [];
select(fields: string): this {
this.query = `SELECT ${fields}`;
return this;
}
from(table: string): this {
this.query += ` FROM ${table}`;
return this;
}
where(condition: string): this {
this.conditions.push(condition);
return this;
}
build(): string {
if (this.conditions.length > 0) {
this.query += ` WHERE ${this.conditions.join(" AND ")}`;
}
return this.query;
}
}
const query = new QueryBuilder()
.select("*")
.from("users")
.where("age > 18")
.where("active = true")
.build();
console.log(query);
// SELECT * FROM users WHERE age > 18 AND active = true
Abstract Methods
Metodi astratti devono essere implementati da sottoclassi.
abstract class Shape {
// Metodo astratto
abstract area(): number;
abstract perimetro(): number;
// Metodo concreto
descrivi(): string {
return `Area: ${this.area()}, Perimetro: ${this.perimetro()}`;
}
}
class Cerchio extends Shape {
constructor(private raggio: number) {
super();
}
area(): number {
return Math.PI * this.raggio ** 2;
}
perimetro(): number {
return 2 * Math.PI * this.raggio;
}
}
class Rettangolo extends Shape {
constructor(private larghezza: number, private altezza: number) {
super();
}
area(): number {
return this.larghezza * this.altezza;
}
perimetro(): number {
return 2 * (this.larghezza + this.altezza);
}
}
Metodi con Rest Parameters
Accettare numero variabile di argomenti.
class Logger {
log(...messaggi: string[]): void {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}]`, ...messaggi);
}
formatta(template: string, ...valori: any[]): string {
return valori.reduce((acc, val, i) => acc.replace(`{${i}}`, val), template);
}
}
const logger = new Logger();
logger.log("Avvio", "applicazione", "completato");
const msg = logger.formatta("Utente {0} ha {1} anni", "Mario", 30);
Metodi con Decoratori
Applicare decoratori per modificare comportamento metodi.
// Decoratore log
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Chiamata ${propertyKey} con:`, args);
const result = originalMethod.apply(this, args);
console.log(`Ritorno:`, result);
return result;
};
}
// Decoratore memoize
function memoize(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
const cache = new Map();
descriptor.value = function (...args: any[]) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = originalMethod.apply(this, args);
cache.set(key, result);
return result;
};
}
class Calculator {
@log
somma(a: number, b: number): number {
return a + b;
}
@memoize
fibonacci(n: number): number {
if (n <= 1) return n;
return this.fibonacci(n - 1) + this.fibonacci(n - 2);
}
}
Conclusioni
I metodi delle classi in TypeScript forniscono meccanismo per incapsulare comportamenti e logica all’interno di strutture orientate agli oggetti. Modificatori di accesso controllano visibilità, metodi statici operano sulla classe, arrow functions risolvono problemi di binding this, mentre getter/setter permettono logica custom per accesso proprietà. Supporto per async/await, generics, overloading, e decoratori rende i metodi flessibili e potenti. Comprendere pattern come method chaining, abstract methods, e uso appropriato di this risulta essenziale per progettare classi robuste e API intuitive in applicazioni TypeScript orientate agli oggetti.