Modelele clasice de proiectare software (GoF – Gang of Four) au fost concepute inițial pentru limbaje cu tipare nominală rigidă precum Java sau C++. În TypeScript, combinarea tipării structurale, a uniunilor discriminate și a parametrilor de tip fantomă (Phantom Types) permite implementarea unor versiuni mult mai elegante, mai sigure și complet verificate la compilare ale acestor modele.
// Problema clasică cu Builder: Cum garantăm la compilare că utilizatorul a setat
// câmpurile obligatorii înainte de a apela .build()?
// Soluție: Folosim Phantom Types pentru a urmări starea internă!
interface ComandaFinala {
client: string;
total: number;
adresaLivrare: string;
}
// Folosim un generic 'Stare' pentru a reține câmpurile completate:
class ComandaBuilder<Stare extends { client?: string; total?: number; adresaLivrare?: string } = {}> {
private date: Partial<ComandaFinala> = {};
setClient(client: string): ComandaBuilder<Stare & { client: string }> {
this.date.client = client;
return this as any;
}
setTotal(total: number): ComandaBuilder<Stare & { total: number }> {
this.date.total = total;
return this as any;
}
setAdresa(adresa: string): ComandaBuilder<Stare & { adresaLivrare: string }> {
this.date.adresaLivrare = adresa;
return this as any;
}
// Metoda .build() este activată DOAR dacă toate cele 3 chei obligatorii sunt prezente în 'Stare':
build(this: ComandaBuilder<{ client: string; total: number; adresaLivrare: string }>): ComandaFinala {
return this.date as ComandaFinala;
}
}
// TESTARE:
const builder = new ComandaBuilder()
.setClient("Ion Ionescu")
.setTotal(450);
// builder.build();
// EROARE DE COMPILARE: The 'this' context of type is not assignable! Lipsește 'adresaLivrare'!
const comandaGata = builder.setAdresa("Strada Florilor nr. 10").build(); // VALID!
2. The Result Pattern (Tratarea Erorilor ca Valori în loc de throw)
Aruncarea de excepții prin throw new Error() are un mare defect în TypeScript: tipul returnat de funcție nu include excepția aruncată! O funcție cu semnătura function salveaza(): Promise<User> ascunde complet faptul că poate crăpa cu NetworkError sau ValidationError.
Modelul Result (inspirat din limbaje precum Rust sau F#) transformă eșecul într-o valoare returnată explicită:
export type Result<T, E = Error> =
| { readonly ok: true; readonly valoare: T }
| { readonly ok: false; readonly eroare: E };
export function Succes<T>(valoare: T): Result<T, never> {
return { ok: true, valoare };
}
export function Esec<E>(eroare: E): Result<never, E> {
return { ok: false, eroare };
}
// Funcție de business care returnează un Result garantat:
function imparteNumere(a: number, b: number): Result<number, string> {
if (b === 0) return Esec("Împărțirea la zero nu este permisă!");
return Succes(a / b);
}
const calcul = imparteNumere(10, 2);
if (calcul.ok) {
console.log(`Rezultat: ${calcul.valoare}`); // 'calcul.valoare' este de tip number
} else {
console.error(`Eroare: ${calcul.eroare}`); // 'calcul.eroare' este de tip string
}
3. Repository Pattern Type-Safe
Pentru a decupla logica de business de o bază de date anume (Prisma, Drizzle sau TypeORM), definiți o interfață generică abstractă cu operațiuni CRUD puternic tipate:
interface BazaEntitate { id: string | number; }
interface IRepository<T extends BazaEntitate> {
gasesteDupaId(id: T['id']): Promise<T | null>;
gasesteToti(filtru?: Partial<T>): Promise<T[]>;
salveaza(entitate: Omit<T, 'id'>): Promise<T>;
sterge(id: T['id']): Promise<boolean>;
}
Această arhitectură permite înlocuirea imediată a stocării reale cu o implementare în memorie (In-Memory Repository) pentru teste unitare ultra-rapide și deterministe.