Software Engineering

فراتر از کتاب درسی: پیاده‌سازی الگوهای طراحی در معماری‌های نرم‌افزاری مدرن

الگوهای طراحی اغلب به‌عنوان قوانین سفت و سخت یا «طلسم‌های جادویی» که کد مقیاس‌پذیر را تضمین می‌کنند، درک نادرست می‌شوند. در واقع، آن‌ها راه‌حل‌های اثبات‌شده برای مشکلات تکرارشونده در طراحی نرم‌افزار هستند. با این حال، دانستن نام یک الگو تنها نیمی از نبرد است؛ درک چه زمانی و چگونه بدون مهندسی بیش از حد از آن استفاده کنیم، جایی است که واقعاً ارشدیت نشان داده می‌شود. این پست به سه دسته اصلی الگوهای طراحی—ساختاری (Creational)، ترکیبی (Structural) و رفتاری (Behavioral)—با تمرکز بر پیاده‌سازی عملی به جای تعاریف صرفاً نظری می‌پردازد.

الگوهای ساختاری (Creational): مدیریت ایجاد اشیاء

الگوهای ساختاری (Creational) با مکانیزم‌های ایجاد اشیاء سروکار دارند و سعی می‌کنند اشیاء را به شیوه‌ای مناسب برای موقعیت ایجاد کنند. رایج‌ترین آن‌ها روش کارخانه (Factory Method) است. به جای آنکه مستقیماً اشیاء را نمونه‌سازی کنید، از یک کارخانه می‌خواهید آن‌ها را برای شما بسازد. این کار کد کلاینت را از کلاس‌های عینی جدا می‌کند و سیستم را قابل‌توسعه‌تر می‌سازد.

class NotificationService {
    public send() { /* ... */ }
}

class EmailNotification extends NotificationService {
    public send() { console.log("Email sent"); }
}

class SMSService extends NotificationService {
    public send() { console.log("SMS sent"); }
}

class NotificationFactory {
    public static create(type: string): NotificationService {
        switch(type) {
            case 'email': return new EmailNotification();
            case 'sms': return new SMSService();
            default: throw new Error("Invalid type");
        }
    }
}

// Usage
const notifier = NotificationFactory.create('email');
notifier.send();

این رویکرد به شما اجازه می‌دهد تا انواع جدید اعلان (مانند اعلان‌های Push) را بدون تغییر در کد کلاینت موجود اضافه کنید، که با اصل باز/بسته (Open/Closed Principle) همخوانی دارد.

الگوهای ترکیبی (Structural): ترکیب اشیاء و کلاس‌ها

الگوهای ترکیبی (Structural) به چگونگی ترکیب کلاس‌ها و اشیاء برای تشکیل ساختارهای بزرگ‌تر می‌پردازند. الگوی تزئین‌گر (Decorator Pattern) مورد علاقه بسیاری از توسعه‌دهندگان است زیرا به شما امکان می‌دهد عملکرد یک شیء را بدون تغییر در ساختار آن گسترش دهید. این اغلب جایگزین بهتری برای وراثت است وقتی نیاز دارید مسئولیت‌ها را به‌صورت پویا اضافه کنید.

interface Coffee {
    cost(): number;
    description(): string;
}

class BasicCoffee implements Coffee {
    cost() { return 1.50; }
    description() { return "Black Coffee"; }
}

class MilkDecorator implements Coffee {
    private component: Coffee;
    constructor(component: Coffee) { this.component = component; }

    cost() { return this.component.cost() + 0.50; }
    description() { return this.component.description() + " + Milk"; }
}

class SugarDecorator implements Coffee {
    private component: Coffee;
    constructor(component: Coffee) { this.component = component; }

    cost() { return this.component.cost() + 0.10; }
    description() { return this.component.description() + " + Sugar"; }
}

const coffee = new SugarDecorator(new MilkDecorator(new BasicCoffee()));
console.log(coffee.description()); // "Black Coffee + Milk + Sugar"

الگوهای رفتاری (Behavioral): ارتباط بین اشیاء

الگوهای رفتاری (Behavioral) به الگوریتم‌ها و تخصیص مسئولیت‌ها بین اشیاء می‌پردازند. الگوی استراتژی (Strategy Pattern) به شما امکان می‌دهد یک خانواده از الگوریتم‌ها را تعریف کنید، هر یک را کپسوله‌سازی کنید و آن‌ها را در زمان اجرا قابل تعویض کنید. این برای سناریوهایی که در آن‌ها رفتار یک شیء باید بر اساس زمینه تغییر کند، مانند الگوریتم‌های مختلف مرتب‌سازی یا روش‌های پردازش پرداخت، ایده‌آل است.

interface PaymentStrategy {
    pay(amount: number): void;
}

class CreditCardPayment implements PaymentStrategy {
    pay(amount: number) { console.log(`Paid $${amount} via Credit Card`); }
}

class PayPalPayment implements PaymentStrategy {
    pay(amount: number) { console.log(`Paid $${amount} via PayPal`); }
}

class ShoppingCart {
    private paymentStrategy: PaymentStrategy;

    setPaymentMethod(strategy: PaymentStrategy) {
        this.paymentStrategy = strategy;
    }

    checkout(amount: number) {
        this.paymentStrategy.pay(amount);
    }
}

نتیجه‌گیری

الگوهای طراحی درباره نمایش دانش نیستند؛ آن‌ها درباره نوشتن کد قابل‌نگهداری و انعطاف‌پذیر هستند. کلید تسلط بر آن‌ها این است که مشکلاتی را که حل می‌کنند، شناسایی کنید، پیش از آنکه به خود راه‌حل دست بزنید. با پیشرفت در شغل خود، خواهید یافت که شما نه تنها الگوها را «استفاده» می‌کنید، بلکه تصمیمات معماری شما به‌طور طبیعی با آن‌ها همسو می‌شوند. با پیاده‌سازی‌های ساده شروع کنید، وقتی پیچیدگی ظاهر شد به سمت این ساختارها بازطراحی کنید و همیشه سادگی را بر چابکی ترجیح دهید.

Share: