Metodi Classi

Edoardo Midali
Edoardo Midali

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.