هر بار که یک پروژه شی‌گرا را برای بازبینی فنی می‌گیرم، اولین کاری که می‌کنم نگاه‌کردن به فهرست کلاس‌ها نیست؛ به این نگاه می‌کنم که هر یک از چهار اصل اصلی OOP کجا و چگونه اجرا شده است. در بیش از صد پروژه‌ای که از نزدیک دیده‌ام، تقریباً همیشه یکی از این چهار اصل — معمولاً کپسوله‌سازی یا انتزاع — به نفع سرعت اولیه قربانی شده و چند ماه بعد، همان بخش‌ها به گلوگاه نگهداری تبدیل شده است. اصول چهارگانه، تعارف کتاب‌های درسی نیستند؛ معیار عملی برای ارزیابی طراحی شما هستند.

اصول چهارگانه OOP چیست و چرا اهمیت دارد؟

برنامه‌نویسی شی‌گرا (Object-Oriented Programming) بر چهار اصل بنیادین استوار است: کپسوله‌سازی (Encapsulation)، وراثت (Inheritance)، پلی‌مورفیسم (Polymorphism) و انتزاع (Abstraction). اگر با مفهوم کلی OOP آشنایی ندارید، پیش از ادامه برنامه‌نویسی شی‌گرا را با مثال‌های ساده بفهمید را مطالعه کنید؛ چرا که در این مقاله فرض بر آن است که مفاهیم کلاس و شیء را می‌شناسید و می‌خواهید به عمق اصول چهارگانه نفوذ کنید.

اهمیت این چهار اصل زمانی آشکار می‌شود که کد شما از چند صد خط بگذرد. در مقیاس کوچک، می‌توانید بدون رعایت هیچ‌کدام از این اصول هم نرم‌افزار کارآمد بنویسید. اما در مقیاس بزرگ — پروژه‌هایی که چند توسعه‌دهنده ماه‌ها روی آن‌ها کار می‌کنند — این چهار اصل به یک زبان مشترک تبدیل می‌شوند. زبان مشترکی که به شما می‌گوید کجا مسئولیت یک کلاس تمام می‌شود، کجا وابستگی‌ها باید شکسته شوند و کجا تغییرات آینده کم‌هزینه‌تر خواهند بود.

تجربه عملی من این است: از این چهار اصل، دو اصل اول (کپسوله‌سازی و وراثت) توسط تازه‌کارها بیش از حد استفاده می‌شود و دو اصل آخر (پلی‌مورفیسم و انتزاع) نادیده گرفته می‌شود. دقیقاً برعکس آن‌چه باید. در ادامه هر اصل را با جزئیات، مثال‌های عملی و نشانه‌های کاربرد نادرست بررسی می‌کنیم.

اصل اول: کپسوله‌سازی (Encapsulation)

کپسوله‌سازی به معنای بسته‌بندی داده و رفتار در یک واحد و پنهان‌کردن جزئیات درونی از دنیای بیرون است. سطح دسترسی (Access Modifiers) ابزار اصلی این کار هستند: private، protected و public. برای درک جامع این مفهوم می‌توانید صفحه Encapsulation در ویکی‌پدیا را ببینید، اما تعریف عملی من از این اصل در پروژه‌های واقعی ساده‌تر است: هیچ فیلدی نباید بدون دلیل از بیرون قابل‌تغییر باشد.

کاربرد واقعی کپسوله‌سازی

بگذارید یک مثال از دنیای واقعی بیاورم که در پروژه‌های فروشگاهی زیاد دیده‌ام. فرض کنید کلاسی داریم که سبد خرید را مدیریت می‌کند:

class Cart {
    private array $items = [];
    private float $total = 0.0;

    public function addItem(string $sku, int $price, int $quantity): void {
        if ($quantity <= 0) {
            throw new InvalidArgumentException("تعداد باید مثبت باشد");
        }
        if ($price < 0) {
            throw new InvalidArgumentException("قیمت نمی‌تواند منفی باشد");
        }
        $this->items[] = compact("sku", "price", "quantity");
        $this->recalculate();
    }

    public function removeItem(string $sku): void {
        $this->items = array_filter(
            $this->items,
            fn($item) => $item["sku"] !== $sku
        );
        $this->recalculate();
    }

    public function getTotal(): float {
        return $this->total;
    }

    private function recalculate(): void {
        $this->total = array_reduce(
            $this->items,
            fn($sum, $item) => $sum + ($item["price"] * $item["quantity"]),
            0.0
        );
    }
}

سه نکته مهم در این مثال وجود دارد که هر سه از دل کپسوله‌سازی بیرون می‌آیند. اول: فیلد items از بیرون قابل دسترسی نیست، بنابراین هیچ‌کس نمی‌تواند بدون عبور از addItem چیزی به سبد اضافه کند. دوم: محاسبه مجموع به‌طور خودکار انجام می‌شود؛ مصرف‌کننده نیازی ندارد بداند چگونه محاسبه می‌شود. سوم: اگر فردا قاعده محاسبه مالیات اضافه شد، فقط recalculate را تغییر می‌دهیم و هیچ کد بیرونی آسیب نمی‌بیند.

همین الگو در همه زبان‌های شی‌گرا وجود دارد. در PHP سطح دسترسی با کلمات کلیدی مشخص می‌شود؛ در Python با یک زیرخط ساده و دو زیرخط برای سطح سختگیرانه‌تر؛ در JavaScript مدرن با علامت #؛ و در TypeScript با همان کلمات کلیدی PHP. اگر با سبک شی‌گرایی PHP آشنایی ندارید، آموزش شی گرایی در php را ببینید تا پیش‌زمینه‌تان کامل شود.

اشتباهات رایج در کپسوله‌سازی

رایج‌ترین اشتباه، ساختن Setter و Getter برای همه فیلدها است. اگر همه فیلدها هم Getter عمومی دارند و هم Setter عمومی، در واقع کپسوله‌سازی صورت نگرفته است؛ فقط ظاهر آن را ساخته‌اید. کپسوله‌سازی واقعی یعنی عملیات معنادار به‌جای دسترسی مستقیم به داده. مثلاً به‌جای setBalance، متد deposit و withdraw ارائه می‌دهیم.

اگر برای هر فیلد کلاس یک Getter و Setter عمومی گذاشته‌اید، کلاس شما در واقع یک ساختار داده با تزئینات است، نه یک موجودیت شی‌گرا. کپسوله‌سازی یعنی عملیات معنادار، نه دسترسی خام.

اصل دوم: وراثت (Inheritance)

وراثت به یک کلاس اجازه می‌دهد ویژگی‌ها و رفتارهای کلاس دیگری را به ارث ببرد. کلاس فرزند (Child) می‌تواند هم رفتارهای والد (Parent) را استفاده کند، هم آن‌ها را بازنویسی (Override) کند و هم رفتارهای جدید اضافه کند. وراثت، ابزار اصلی حذف تکرار کد (DRY Principle) است.

کاربرد واقعی وراثت

مثال زیر را در نظر بگیرید. ما می‌خواهیم چند نوع گزارش‌ساز داشته باشیم که همه از یک ساختار پایه استفاده می‌کنند، اما هر کدام روش تولید گزارش متفاوتی دارند:

abstract class ReportGenerator {
    public function generate(array $data): string {
        $this->validate($data);
        $content = $this->render($data);
        return $this->wrap($content);
    }

    protected function validate(array $data): void {
        if (empty($data)) {
            throw new InvalidArgumentException("داده خالی است");
        }
    }

    abstract protected function render(array $data): string;

    protected function wrap(string $content): string {
        return "--- گزارش ---\n" . $content . "\n--- پایان ---";
    }
}

class PdfReport extends ReportGenerator {
    protected function render(array $data): string {
        return "گزارش PDF با " . count($data) . " ردیف";
    }
}

class ExcelReport extends ReportGenerator {
    protected function render(array $data): string {
        return "گزارش اکسل با " . count($data) . " ردیف";
    }
}

در این مثال، منطق مشترک (اعتبارسنجی و قالب‌بندی) در والد قرار گرفته و منطق مخصوص هر نوع گزارش در فرزند. اگر فردا فرمت جدیدی اضافه شود، فقط یک کلاس جدید می‌نویسیم و بخش مشترک دست نمی‌خورد.

خطر وراثت عمیق

یکی از بزرگ‌ترین دام‌های وراثت، ساخت سلسله‌مراتب‌های عمیق است. وقتی چهار یا پنج کلاس در یک زنجیره وراثتی قرار می‌گیرند، فهم اینکه یک متد از کجا آمده تقریباً غیرممکن می‌شود. در بیش از دو دهه تجربه، دیده‌ام که بیش از دو یا سه لایه وراثت تقریباً همیشه به بدهی فنی منجر می‌شود. راه‌حل جایگزین، ترکیب (Composition) است که در بخش ماتریس کاربرد به آن می‌رسیم.

اصل سوم: پلی‌مورفیسم (Polymorphism)

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

دو نوع پلی‌مورفیسم

در عمل دو نوع پلی‌مورفیسم داریم. اول، پلی‌مورفیسم زمان کامپایل (Compile-Time) که معمولاً به‌شکل Overloading متدها ظاهر می‌شود — یعنی چند متد با نام یکسان اما پارامترهای متفاوت. دوم، پلی‌مورفیسم زمان اجرا (Runtime) که از طریق بازنویسی متد در فرزندان اتفاق می‌افتد و پرکاربردترین شکل این اصل در پروژه‌های واقعی است.

interface Notifier {
    public function send(string $message): bool;
}

class EmailNotifier implements Notifier {
    public function send(string $message): bool {
        // ارسال ایمیل
        return true;
    }
}

class SmsNotifier implements Notifier {
    public function send(string $message): bool {
        // ارسال پیامک
        return true;
    }
}

class PushNotifier implements Notifier {
    public function send(string $message): bool {
        // ارسال نوتیفیکیشن
        return true;
    }
}

function broadcast(array $notifiers, string $message): void {
    foreach ($notifiers as $notifier) {
        $notifier->send($message);
    }
}

تابع broadcast هیچ‌وقت نمی‌داند با کدام نوع Notifier صحبت می‌کند. اگر فردا SlackNotifier اضافه شد، بدون تغییر این تابع در سیستم جای می‌گیرد. این همان چیزی است که در معماری نرم‌افزار به آن Open/Closed Principle می‌گویند: باز برای افزودن، بسته برای تغییر.

پلی‌مورفیسم در زبان‌های مختلف

در Python که از تایپ پویا استفاده می‌کند، پلی‌مورفیسم با Duck Typing پیاده می‌شود: اگر شیئی متد مورد انتظار را داشته باشد، کافی است. این انعطاف را در شی گرایی در پایتون با مثال‌های عملی‌تر توضیح داده‌ام. در JavaScript، پلی‌مورفیسم از همان ابتدا در DNA زبان بوده است و در شی گرایی در جاوااسکریپت به‌طور دقیق بررسی شده است.

پلی‌مورفیسم یعنی می‌توانید به شیئی بگویید چه کار کن، بدون آنکه نگران باشید این شیء دقیقاً از چه کلاسی است. اگر مجبورید قبل از صدا زدن متد بررسی کنید که چه نوع شیئی دارید، پلی‌مورفیسم شما ناقص است.

اصل چهارم: انتزاع (Abstraction)

انتزاع یعنی تمرکز بر چیستی یک رفتار، نه چگونگی آن. این اصل، بالاترین سطح از چهار اصل است، چون بر پایه سه اصل دیگر بنا می‌شود. ابزارهای اصلی انتزاع، اینترفیس (Interface) و کلاس انتزاعی (Abstract Class) هستند.

انتزاع در سطح معماری

در پروژه‌های بزرگ، انتزاع تنها در سطح متدهای یک کلاس دیده نمی‌شود؛ در سطح معماری کل سیستم هم ظاهر می‌شود. لایه‌های سرویس، Repository، Service Provider و Adapter همه از انتزاع استفاده می‌کنند تا وابستگی‌ها را معکوس کنند. برای دیدن این الگو در عمل، مطالعه ORM چیست و چگونه کار با دیتابیس را ساده می‌کند مفید است؛ ORM خودش یک لایه انتزاعی روی دیتابیس است که جزئیات SQL را از شما پنهان می‌کند.

نمونه‌ای از انتزاع واقعی

interface CacheDriver {
    public function get(string $key): mixed;
    public function set(string $key, mixed $value, int $ttl = 3600): void;
    public function delete(string $key): void;
}

class RedisCache implements CacheDriver {
    public function get(string $key): mixed { /* ... */ }
    public function set(string $key, mixed $value, int $ttl = 3600): void { /* ... */ }
    public function delete(string $key): void { /* ... */ }
}

class FileCache implements CacheDriver {
    public function get(string $key): mixed { /* ... */ }
    public function set(string $key, mixed $value, int $ttl = 3600): void { /* ... */ }
    public function delete(string $key): void { /* ... */ }
}

کد مصرف‌کننده فقط با CacheDriver سر و کار دارد. امروز روی Redis کار می‌کند، فردا می‌توانید به FileCache سوییچ کنید یا یک MemcachedCache اضافه کنید، بدون آنکه یک خط از منطق کسب‌وکار تغییر کند. این جداسازی، بزرگ‌ترین دستاورد انتزاع در مهندسی نرم‌افزار است.

مرز انتزاع بیش از حد

درست همان‌طور که انتزاع کم خطرناک است، انتزاع بیش از حد هم مشکل‌ساز می‌شود. وقتی برای هر عمل کوچکی یک اینترفیس می‌سازید، کد به جای خواناتر شدن، پرحجم‌تر می‌شود. قاعده عملی من: حداقل دو پیاده‌سازی واقعی لازم است تا ساخت اینترفیس توجیه شود. اگر فقط یک پیاده‌سازی دارید، شاید هنوز وقتش نرسیده است. در TypeScript این تصمیم‌گیری را می‌توانید در اینترفیس در تایپ اسکریپت با مثال‌های بیشتر دنبال کنید.

چهار اصل در تعامل با هم

نکته کلیدی که در بسیاری از منابع آموزشی فارسی کمتر به آن پرداخته می‌شود این است که این چهار اصل مستقل نیستند؛ روی یک پیوستار کنار هم کار می‌کنند. کپسوله‌سازی، داده را امن می‌کند؛ انتزاع، قرارداد را بیرون می‌کشد؛ وراثت، اشتراک کد را ممکن می‌سازد؛ و پلی‌مورفیسم، از انتزاع و وراثت با هم، یک سیستم انعطاف‌پذیر می‌سازد.

اصلهدف اصلیابزار اصلینشانه نبود آن
کپسوله‌سازیپنهان‌کردن جزئیاتسطح دسترسیدسترسی مستقیم به فیلدها
وراثتحذف تکرار کدextends / inheritکد تکراری در چند کلاس
پلی‌مورفیسمانعطاف‌پذیریOverride و Interfaceشرط‌های if/else طولانی
انتزاعجداسازی قرارداد از پیاده‌سازیInterface و Abstractوابستگی مستقیم به کلاس‌های مشخص

اگر در کد خود شرط‌های if ($type === "email") یا switch ($kind) طولانی می‌بینید، نشانه شکست پلی‌مورفیسم است. اگر در چند کلاس منطق مشابه تکراری دارید، نشانه شکست وراثت (یا ترکیب) است. اگر تغییر یک فیلد داخلی، چند نقطه از کد بیرونی را می‌شکند، کپسوله‌سازی شکسته شده است. و اگر افزودن یک پیاده‌سازی جدید، نیاز به تغییر چند فایل دارد، انتزاع ناقص است.

ماتریس کاربرد: هر اصل کجا و چه زمانی؟

هر یک از این چهار اصل، حوزه کاربردی متفاوتی دارند. بیایید ببینیم هر یک در چه موقعیت‌هایی بیشترین ارزش را می‌سازند و کجا استفاده از آن‌ها می‌تواند مضر باشد.

کپسوله‌سازی را جدی بگیرید وقتی:

  • کلاس شما حالت داخلی (State) دارد که باید معتبر بماند
  • پیاده‌سازی ممکن است در آینده تغییر کند
  • داده حساس یا مالی در میان است
  • می‌خواهید از دسترسی غیرمجاز جلوگیری کنید

وراثت را به‌کار ببرید وقتی:

  • چند کلاس رابطه is-a واضحی دارند
  • منطق مشترک قابل توجهی بین آن‌ها وجود دارد
  • سلسله‌مراتب طبیعی است و بیش از دو یا سه لایه عمق نمی‌گیرد

پلی‌مورفیسم را وارد کنید وقتی:

  • شرط‌های نوع (Type Checking) در کد زیاد شده است
  • می‌خواهید افزودن کلاس جدید، نیازی به تغییر کد قدیمی نداشته باشد
  • چند الگوریتم هم‌خانواده دارید که از یک رابط واحد پیروی می‌کنند

انتزاع را جدی بگیرید وقتی:

  • کد شما باید از جزئیات پیاده‌سازی مستقل بماند
  • می‌خواهید قابلیت تست‌پذیری را افزایش دهید
  • چند پیاده‌سازی واقعی در آینده پیش‌بینی می‌شود
  • با لایه‌های بیرونی مثل دیتابیس، API یا فایل سیستم سر و کار دارید

در مقابل، هر جا بیش از حد روی این اصول تأکید کنید، دام Over-Engineering (مهندسی بیش از حد) در کمین است. مثلاً ساخت یک اینترفیس با هفت متد که فقط یک کلاس آن را پیاده می‌کند، یا ساخت یک سلسله‌مراتب وراثت چهارلایه برای صرفه‌جویی در پنج خط کد، هر دو نمونه‌های رایج این دام هستند.

سناریوی واقعی: طراحی یک سیستم پرداخت

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

interface PaymentGateway {
    public function pay(float $amount, array $meta): PaymentResult;
    public function refund(string $transactionId, float $amount): PaymentResult;
}

abstract class BaseGateway implements PaymentGateway {
    protected string $apiKey;
    protected int $timeout = 30;

    public function __construct(string $apiKey) {
        $this->apiKey = $apiKey;
    }

    public function pay(float $amount, array $meta): PaymentResult {
        $this->validate($amount);
        return $this->doPay($amount, $meta);
    }

    protected function validate(float $amount): void {
        if ($amount <= 0) {
            throw new InvalidArgumentException("مبلغ نامعتبر");
        }
    }

    abstract protected function doPay(float $amount, array $meta): PaymentResult;
}

حالا در این ساختار، هر اصل نقش مشخصی دارد:

  • کپسوله‌سازی: فیلد apiKey از بیرون قابل دسترسی نیست و مقدار timeout توسط فرزندان قابل تغییر است.
  • وراثت: BaseGateway منطق مشترک (اعتبارسنجی و انصراف) را در اختیار فرزندان می‌گذارد.
  • پلی‌مورفیسم: هر درگاه، رفتار doPay خود را دارد و کد مصرف‌کننده بدون دانستن نوع درگاه، از آن استفاده می‌کند.
  • انتزاع: کد مصرف‌کننده فقط با اینترفیس PaymentGateway کار می‌کند و از جزئیات API هر درگاه بی‌خبر است.

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

همین الگو در پروژه‌های وردپرسی و پلاگین‌نویسی هم بسیار کاربرد دارد. اگر روی افزونه‌های وردپرس کار می‌کنید، بهترین روش‌های PHP مدرن را مطالعه کنید تا ببینید این اصول در اکوسیستم واقعی چطور پیاده می‌شوند.

اشتباهات رایج در پیاده‌سازی اصول چهارگانه

در بازبینی کد تیم‌های مختلف، الگوهای تکراری از خطا را دیده‌ام که تقریباً همه‌شان به سوءاستفاده از یکی از این چهار اصل برمی‌گردند. فهرست زیر خلاصه این خطاها است.

اشتباهات مربوط به کپسوله‌سازی

  • ساختن Getter و Setter برای همه فیلدها بدون هیچ منطق اعتبارسنجی
  • استفاده از فیلدهای public به بهانه سرعت نوشتن
  • بازگرداندن آرایه‌های داخلی به‌طور مستقیم، به‌جای کپی

اشتباهات مربوط به وراثت

  • ساختن سلسله‌مراتب عمیق‌تر از سه لایه
  • استفاده از وراثت برای صرفه‌جویی در چند خط کد بی‌ربط
  • نقض Liskov Substitution Principle با بازنویسی رفتارهای غیرمنتظره
  • استفاده از وراثت به‌جای ترکیب در جایی که رابطه is-a واقعی نیست

اشتباهات مربوط به پلی‌مورفیسم

  • بررسی نوع کلاس با instanceof یا typeof در کد مصرف‌کننده
  • ساختن متدهای با نام یکسان اما رفتار کاملاً متفاوت که قابل پیش‌بینی نیست
  • نداشتن یک قرارداد مشترک (Interface یا Abstract) بین پیاده‌سازی‌ها

اشتباهات مربوط به انتزاع

  • ساخت اینترفیس برای هر کلاس کوچک بدون دلیل
  • طراحی اینترفیس‌های پرحجم با ده‌ها متد که فقط بخشی از آن‌ها استفاده می‌شوند
  • نشتی جزئیات پیاده‌سازی به داخل قرارداد (Leaky Abstraction)
در هر یک از این چهار اصل، هدف واقعی ساده‌سازی است، نه پیچیده‌سازی. اگر استفاده از یک اصل باعث می‌شود کد شما سخت‌تر فهمیده شود، به احتمال زیاد آن اصل را در جای اشتباه به‌کار برده‌اید.

رابطه اصول چهارگانه با SOLID

یکی از سوالات پرتکرار این است که آیا اصول چهارگانه OOP همان SOLID هستند؟ پاسخ نه، اما با آن‌ها گره خورده‌اند. اصول چهارگانه، پایه‌های شی‌گرایی هستند که دهه‌ها قبل از SOLID معرفی شدند. SOLID در دهه ۲۰۰۰ توسط Robert C. Martin به‌عنوان پنج اصل طراحی کلاس‌ها معرفی شد و همه‌شان بر پایه چهار اصل OOP ساخته شده‌اند.

برای مثال، Single Responsibility Principle بدون کپسوله‌سازی معنا ندارد، چون مسئولیت‌ها را باید از هم جدا کرد. Open/Closed Principle از پلی‌مورفیسم تغذیه می‌کند. Liskov Substitution بدون وراثت قابل بحث نیست و Interface Segregation در سطح انتزاع کار می‌کند. بنابراین می‌توان گفت اصول چهارگانه، بنیاد نظری SOLID هستند.

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

پرسش‌های پرتکرار درباره اصول چهارگانه OOP

آیا همه پروژه‌ها به هر چهار اصل نیاز دارند؟

نه. پروژه‌های کوچک ممکن است تنها با یکی دو اصل کار کنند. اما وقتی پروژه به بیش از ۱۰ کلاس و چند توسعه‌دهنده می‌رسد، چهار اصل تقریباً اجباری می‌شوند. تجربه من این است که نادیده‌گرفتن یکی از چهار اصل در یک پروژه بزرگ، معمولاً در همان سه ماه اول خودش را نشان می‌دهد.

کدام یک از این چهار اصل را اول باید یاد گرفت؟

ترتیب منطقی یادگیری: کپسوله‌سازی، وراثت، انتزاع، پلی‌مورفیسم. اما در عمل، هر چهار اصل به هم تنیده‌اند و نمی‌توانید یکی را کامل یاد بگیرید بدون آنکه حداقل آشنایی سطحی با بقیه داشته باشید. توصیه من این است که از کپسوله‌سازی شروع کنید، چون مستقیماً روی کیفیت کد اثر می‌گذارد.

آیا پلی‌مورفیسم همیشه به انتزاع نیاز دارد؟

خیر. پلی‌مورفیسم از طریق بازنویسی متد (Override) هم می‌تواند بدون انتزاع رخ دهد. اما در پروژه‌های بزرگ، ترکیب انتزاع (Interface) با پلی‌مورفیسم، انعطاف‌پذیری بی‌نظیری می‌سازد. تجربه من این است که هر جا پلی‌مورفیسم بدون انتزاع به‌کار رفته، چند ماه بعد به انتزاع رسیده است.

چرا در بعضی پروژه‌ها وراثت کاملاً نفی می‌شود؟

این نفی مطلق از چند منبع سرچشمه می‌گیرد؛ یکی از آن‌ها حرکت جامعه به سمت زبان‌هایی مثل Go و Rust است که وراثت را در سطح کلاس ندارند. دیگری، درس‌هایی است که در پروژه‌های بزرگ از سلسله‌مراتب عمیق گرفته شده. اما در واقع وراثت نفی نمی‌شود؛ ترکیب در جای درست، وراثت در جای درست. برای دیدن دیدگاه مقابل، برنامه‌نویسی تابعی چیست و چه مزایایی دارد را بخوانید.

تفاوت انتزاع و کپسوله‌سازی چیست؟

کپسوله‌سازی درباره پنهان‌کردن داده است؛ انتزاع درباره پنهان‌کردن پیچیدگی. کپسوله‌سازی می‌گوید فیلد داخلی را از بیرون قفل کن؛ انتزاع می‌گوید مصرف‌کننده نباید بداند درون متد چه خبر است. کپسوله‌سازی، پیاده‌سازی است؛ انتزاع، طراحی است.

آیا استفاده از Abstract Class همیشه بهتر از Interface است؟

نه. Abstract Class وقتی خوب است که منطق مشترک واقعی وجود داشته باشد. Interface وقتی خوب است که فقط قرارداد لازم دارید و هیچ پیاده‌سازی مشترکی ندارید. ترکیب هر دو هم رایج است: یک Interface به‌عنوان قرارداد و یک Abstract Class برای منطق مشترک پیاده‌سازی کنندگان.

در TypeScript، استفاده از Interface با Generics چه مزیتی دارد؟

ترکیب این دو، شما را قادر می‌سازد قراردادهای عمومی و قابل استفاده در انواع مختلف بسازید. مثلاً Repository<T> می‌تواند برای هر نوع داده استفاده شود، بدون تکرار قرارداد. این الگو در جنریک در تایپ اسکریپت با مثال‌های عملی‌تر توضیح داده شده است.

برای مدل‌سازی موجودیت‌های دنیای واقعی، چه الگویی را پیشنهاد می‌کنید؟

ترکیب چهار اصل با الگوهای معماری. برای مثال، در معماری MVC، مدل‌ها معمولاً از طریق کپسوله‌سازی و انتزاع طراحی می‌شوند، در حالی که کنترلرها بیشتر از پلی‌مورفیسم استفاده می‌کنند. برای درک این ترکیب، پیشنهاد می‌کنم الگوی MVC را با مثال واقعی یاد بگیرید را ببینید.

نگاهی به مسیر پیش رو

اصول چهارگانه OOP، چارچوب فکری‌ای هستند که به شما کمک می‌کنند تصمیم‌های طراحی خود را مستند و قابل دفاع کنید. هر بار که بین دو راه‌حل مردد هستید، پرسیدن این چهار سوال به شما کمک می‌کند: آیا کپسوله‌سازی را حفظ کرده‌ام؟ آیا وراثت را به‌جای ترکیب در جای درست به‌کار برده‌ام؟ آیا پلی‌مورفیسم را جانشین شرط‌های تکراری کرده‌ام؟ و آیا انتزاع را در سطح درست نگه داشته‌ام؟

تجربه شخصی من این است که بهترین کدها، نه با سخت‌گیری مطلق روی هر چهار اصل به‌طور همزمان نوشته می‌شوند، بلکه با تصمیم‌گیری آگاهانه در هر لحظه: کجا باید سختگیر باشیم و کجا باید انعطاف نشان دهیم. تازه‌کارها معمولاً یا هیچ‌کدام از این اصول را رعایت نمی‌کنند یا هر چهار را به‌طور افراطی. حرفه‌ای‌ها بین این دو حالت، دائم تصمیم می‌گیرند و تصمیم‌هایشان را می‌توانند توجیه کنند.

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