TypeScript / SOLID зарчмууд

SOLID зарчмууд

SOLID нь цэвэр, уян хатан, дахин ашиглагдах код бичих таван зарчмын товчлол юм. Эдгээр зарчмыг дагавал кодыг өөрчлөх, тест бичих, баг хамтран ажиллах нь хялбар болдог. TypeScript-ийн interface болон төрлийн систем нь SOLID зарчмуудыг хэрэгжүүлэхэд маш тохиромжтой.

Таван зарчим бүрийг жишээтэйгээр авч үзье.

S — Single Responsibility Principle

Нэг класс зөвхөн нэг л зүйлийн хариуцлагатай байна.

typescript
// ❌ Буруу — нэг класс хэт олон зүйл хийж байна
class ХэрэглэгчийнМенежер {
  хэрэглэгчийгХадгалах(хэрэглэгч: object) { /* датабааст хадгалах */ }
  и_мэйлИлгээх(и_мэйл: string) { /* и-мэйл илгээх */ }
  лог(мэдэгдэл: string) { /* лог бичих */ }
  нэвтрэлтШалгах(токен: string) { /* auth шалгах */ }
}

// ✅ Зөв — хариуцлага тус тусдаа хуваагдсан
class ХэрэглэгчийнБаазын_Үйлчилгээ {
  хэрэглэгчийгХадгалах(хэрэглэгч: object): Promise<void> {
    // Зөвхөн датабааcтай ажиллана
    return Promise.resolve();
  }
}

class И_МэйлийнҮйлчилгээ {
  илгээх(и_мэйл: string, мэдэгдэл: string): void {
    // Зөвхөн и-мэйл илгээнэ
    console.log(`→ ${и_мэйл}: ${мэдэгдэл}`);
  }
}

class ЛогийнҮйлчилгээ {
  бичих(мэдэгдэл: string): void {
    // Зөвхөн лог бичнэ
    console.log(`[ЛОГ] ${new Date().toISOString()}: ${мэдэгдэл}`);
  }
}

O — Open/Closed Principle

Код өргөтгөлд нээлттэй, өөрчлөлтөд хаалттай байна. Шинэ функциональ нэмэхийн тулд одоо байгаа кодыг өөрчлөхгүй, шинэ класс нэмнэ.

typescript
interface ХөнгөлөлтийнТооцоогч {
  тооцох(үнэ: number): number;
}

class ЭнгийнХөнгөлөлт implements ХөнгөлөлтийнТооцоогч {
  тооцох(үнэ: number): number {
    return үнэ * 0.9; // 10% хөнгөлөлт
  }
}

class ProХөнгөлөлт implements ХөнгөлөлтийнТооцоогч {
  тооцох(үнэ: number): number {
    return үнэ * 0.7; // 30% хөнгөлөлт
  }
}

// Шинэ хөнгөлөлт нэмэхдээ энэ классыг өөрчлөхгүй
class ЗахиалгаТооцоогч {
  constructor(private хөнгөлөлт: ХөнгөлөлтийнТооцоогч) {}

  нийтДүн(үнэ: number): number {
    return this.хөнгөлөлт.тооцох(үнэ);
  }
}

// Шинэ хөнгөлөлтийн хэлбэр нэмэхэд ЗахиалгаТооцоогч өөрчлөгдөхгүй
class БаярынХөнгөлөлт implements ХөнгөлөлтийнТооцоогч {
  тооцох(үнэ: number): number {
    return үнэ * 0.5; // 50% Цагаан Сар хөнгөлөлт
  }
}

const захиалга = new ЗахиалгаТооцоогч(new БаярынХөнгөлөлт());
console.log(захиалга.нийтДүн(10000)); // 5000

L — Liskov Substitution Principle

Хүүхэд класс нь эцэг классын оронд ямар ч алдаагүй орж чадах ёстой.

typescript
abstract class Дүрс {
  abstract талбай(): number;
  тайлбар(): string {
    return `Талбай: ${this.талбай()}`;
  }
}

class Тэгш_Өнцөгт extends Дүрс {
  constructor(private өргөн: number, private өндөр: number) {
    super();
  }
  талбай(): number {
    return this.өргөн * this.өндөр;
  }
}

class Тойрог extends Дүрс {
  constructor(private радиус: number) {
    super();
  }
  талбай(): number {
    return Math.PI * this.радиус ** 2;
  }
}

// Дүрс хүлээн авдаг функц — аль ч хүүхэд классаар ажиллана
function талбайХэвлэх(дүрс: Дүрс): void {
  console.log(дүрс.тайлбар()); // аль нь ч байсан зөв ажиллана
}

талбайХэвлэх(new Тэгш_Өнцөгт(4, 5)); // "Талбай: 20"
талбайХэвлэх(new Тойрог(3));          // "Талбай: 28.27..."

I — Interface Segregation Principle

Нэг том interface-ийн оронд олон жижиг, тусгай interface ашиглана.

typescript
// ❌ Буруу — хэт том interface
interface Ажилтан {
  нэр: string;
  цалин(): number;
  код_бичих(): void;     // бүх ажилтан код бичдэггүй
  борлуулах(): void;     // бүх ажилтан борлуулдаггүй
  менежмент(): void;     // бүх ажилтан менежер биш
}

// ✅ Зөв — тусгайлсан interface-үүд
interface ҮндсэнАжилтан {
  нэр: string;
  цалин(): number;
}

interface Хөгжүүлэгч extends ҮндсэнАжилтан {
  код_бичих(): void;
  code_review_хийх(): void;
}

interface БорлуулагчАжилтан extends ҮндсэнАжилтан {
  борлуулах(): void;
  харилцагчТэй_уулзах(): void;
}

// Класс зөвхөн хэрэгтэй interface-ийг хэрэгжүүлнэ
class TypeScriptХөгжүүлэгч implements Хөгжүүлэгч {
  нэр = "Болд";
  цалин() { return 3_000_000; }
  код_бичих() { console.log("TypeScript бичиж байна..."); }
  code_review_хийх() { console.log("PR шалгаж байна..."); }
}

D — Dependency Inversion Principle

Дээд түвшний модуль нь доод түвшний модулиас биш, abstract-аас хамаарна.

typescript
// Abstract давхарга — interface тодорхойлно
interface ХадгалалтынҮйлчилгээ {
  хадгалах(түлхүүр: string, утга: string): Promise<void>;
  авах(түлхүүр: string): Promise<string | null>;
}

// Доод түвшний хэрэгжүүлэлтүүд
class LocalStorageҮйлчилгээ implements ХадгалалтынҮйлчилгээ {
  async хадгалах(түлхүүр: string, утга: string) {
    localStorage.setItem(түлхүүр, утга);
  }
  async авах(түлхүүр: string) {
    return localStorage.getItem(түлхүүр);
  }
}

class SupabaseҮйлчилгээ implements ХадгалалтынҮйлчилгээ {
  async хадгалах(түлхүүр: string, утга: string) {
    // Supabase-д хадгалах
    console.log(`Supabase: ${түлхүүр} = ${утга}`);
  }
  async авах(түлхүүр: string) {
    // Supabase-с авах
    return null;
  }
}

// Дээд түвшний класс — interface-ээс хамаарна, тодорхой классаас биш
class ДэвшилтийнМенежер {
  // ХадгалалтынҮйлчилгээ interface — ямар хэрэгжүүлэлт ч байж болно
  constructor(private хадгалалт: ХадгалалтынҮйлчилгээ) {}

  async дэвшилХадгалах(хичээл: string) {
    await this.хадгалалт.хадгалах(`дэвшил:${хичээл}`, "дууссан");
  }
}

// Хэрэгжүүлэлтийг сольж болно — ДэвшилтийнМенежер өөрчлөгдөхгүй
const менежер1 = new ДэвшилтийнМенежер(new LocalStorageҮйлчилгээ());
const менежер2 = new ДэвшилтийнМенежер(new SupabaseҮйлчилгээ());

Дараагийн хичээлд:

TypeScript дотор алдааг зохицуулах — try/catch, custom error класс, Result pattern ашиглан найдвартай код бичих тухай сурна.