SOLID зарчмууд
SOLID нь цэвэр, уян хатан, дахин ашиглагдах код бичих таван зарчмын товчлол юм. Эдгээр зарчмыг дагавал кодыг өөрчлөх, тест бичих, баг хамтран ажиллах нь хялбар болдог. TypeScript-ийн interface болон төрлийн систем нь SOLID зарчмуудыг хэрэгжүүлэхэд маш тохиромжтой.
Таван зарчим бүрийг жишээтэйгээр авч үзье.
S — Single Responsibility Principle
Нэг класс зөвхөн нэг л зүйлийн хариуцлагатай байна.
// ❌ Буруу — нэг класс хэт олон зүйл хийж байна
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
Код өргөтгөлд нээлттэй, өөрчлөлтөд хаалттай байна. Шинэ функциональ нэмэхийн тулд одоо байгаа кодыг өөрчлөхгүй, шинэ класс нэмнэ.
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
Хүүхэд класс нь эцэг классын оронд ямар ч алдаагүй орж чадах ёстой.
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 ашиглана.
// ❌ Буруу — хэт том 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-аас хамаарна.
// 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 ашиглан найдвартай код бичих тухай сурна.