این نخستین فصل دوره است و عمداً هیچ کدی در آن نمینویسیم. این فصل را یک بار قبل از شروع یادگیری مفاهیم شیگرایی و یک بار هم پس از یادگیری مفاهیم آن مطالعه کنید.
ممکن است عجیب به نظر برسد. شما آمدهاید شیءگرایی یاد بگیرید، و انتظار دارید همین ابتدا با class و self روبهرو شوید. اما تجربه نشان داده کسانی که سینتکس را زودتر از دلیل وجودی آن یاد میگیرند، بعدها به یک نقطهی سردرگمی مشترک میرسند: کد شیءگرا مینویسند بیآنکه بدانند چرا، و در نتیجه کلاسهایی میسازند که فقط ظاهر شیءگرا دارند.
پس در این فصل یک قدم عقب میرویم و به این سوال پاسخ میدهیم: شیءگرایی از کجا آمد، چه دردی را درمان کرد، و چرا با وجود منتقدان جدیاش هنوز زیربنای بخش بزرگی از نرمافزارهای دنیا — از جنگو تا سیستمهای بانکی — است.
در پایان این فصل میدانید:
شیءگرایی دقیقاً چیست و ریشهاش به کدام زبانها برمیگردد
چرا دو نگاه متفاوت (Simula و Smalltalk) شکل امروزی آن را ساختند
منتقدانش چه میگویند و کجای حرفشان درست است
چرا پیادهسازی شیءگرایی در پایتون با جاوا فرق دارد و به روبی نزدیکتر است
چرا یادگیری آن برای ورود به بازار کار عملاً اجباری است
چرا وقت گذاشتن روی تاریخچه و فلسفه ارزشش را دارد؟
فرض کنید تازه استخدام شدهاید و اولین وظیفهتان اضافهکردن یک روش پرداخت جدید به یک سیستم فروشگاهی است. کد را باز میکنید و میبینید توسعهدهندهی قبلی همهچیز را با تابع و متغیر گلوبال نوشته: یک فایل هزار خطی که در آن if payment_type == "card" و if payment_type == "wallet" دهها بار تکرار شده. برای اضافهکردن یک روش تازه باید هر کدام از این شرطها را پیدا کنید و دستی تغییر بدهید — و اگر یکی را جا بیندازید، باگ تولید میشود.
حالا همان سیستم را تصور کنید که در آن هر روش پرداخت یک شیء مستقل است و اضافهکردن روش جدید یعنی ساختن یک کلاس تازه، بدون دستزدن به کد قدیمی.
تفاوت این دو، تفاوت میان کسی است که شیءگرایی را میفهمد و کسی که فقط از آن استفاده میکند. درک تاریخچه و فلسفه به شما کمک میکند:
تصمیم بگیرید کجا کلاس لازم است و کجا یک تابع ساده کافی است
بفهمید چرا پایتون بعضی چیزها را متفاوت با زبانهای دیگر انجام میدهد (و در مستندات گیج نشوید)
نقدهای وارد بر شیءگرایی را بشناسید تا کورکورانه از آن استفاده نکنید
مفاهیمی مثل self، ارثبری و Polymorphism را نه بهعنوان قاعدهی حفظی، بلکه بهعنوان راهحل یک مسئلهی واقعی درک کنید
بدون این درک، شما برنامهنویسی خواهید بود که کد را کپی میکند اما نمیتواند دربارهی آن تصمیم بگیرد.
آشنایی با تاریخچه و فلسفه
مسئلهای که همهچیز از آن شروع شد
اواخر دههی ۱۹۵۰ و آغاز دههی ۱۹۶۰. برنامهنویسی هنوز نوپاست. کد بهصورت دنبالهای از دستورها روی حافظه اجرا میشود؛ متغیرهای گلوبال همهجا در دسترساند و پرشهای GOTO جریان برنامه را به تارعنکبوتی تبدیل میکنند که به آن «کد اسپاگتی» میگفتند.
تا وقتی برنامهها کوچک بودند مشکلی نبود. اما با بزرگتر شدنشان یک بحران ظاهر شد: هیچکس دیگر نمیتوانست کل برنامه را در ذهنش نگه دارد. یک تغییر کوچک در گوشهای از کد، جای دیگری را بهشکل غیرمنتظره خراب میکرد، چون دادهها آزادانه بین همهی توابع دستبهدست میشدند و معلوم نبود چه کسی چه چیزی را تغییر میدهد.
راهحل بدیهی بهنظر میرسید ولی انقلابی بود: چه میشد اگر داده و کدی که روی آن داده کار میکند را کنار هم، در یک واحد بسته میگذاشتیم و اجازه نمیدادیم بیرونیها هرطور خواستند به داده دست بزنند؟
Simula: تولد کلاس و شیء
دو پژوهشگر نروژی، اوله-یوهان دال و کریستن نیگارد، در مرکز محاسبات نروژ روی مسئلهی شبیهسازی کار میکردند — مثلاً شبیهسازی ترافیک یک بندر یا صف مشتریان یک سیستم. برای این کار به روشی نیاز داشتند که هر موجودیت دنیای واقعی (یک کشتی، یک مشتری، یک باجه) را در برنامه بهشکل یک واحد مستقل با داده و رفتار خودش نگه دارد.
نتیجه، زبان Simula 67 (سال ۱۹۶۷) بود و همراهش دو واژهای که تا امروز ماندهاند: class (کلاس) و object (شیء). کلاس، قالبی بود که میگفت هر موجودیت از این نوع چه دادهای دارد و چه کارهایی میتواند بکند؛ و شیء، یک نمونهی واقعی و زنده از آن قالب در حافظه.
این ایدهی بهظاهر ساده، مبنای همهی زبانهای شیءگرای بعدی شد.
Smalltalk: شیءگرایی بهمثابهی پیامرسانی
اگر دال و نیگارد ابزار را ساختند، آلن کی فلسفه را نوشت.
اوایل دههی ۱۹۷۰، آلن کی و همکارانش در آزمایشگاه افسانهای Xerox PARC زبان Smalltalk را ساختند. آلن کی نگاه متفاوتی داشت. برای او مهمترین چیز خود کلاس یا ساختار داده نبود، بلکه این بود که اشیاء چطور با هم حرف میزنند:
«من وقتی اصطلاح «شیءگرا» را ساختم، ذهنم درگیر C++ نبود. ایدهی اصلی، پیامرسانی بود.»
— آلن کی
در این نگاه، هر شیء یک موجود کوچک مستقل است — تقریباً مثل یک سلول زنده یا یک کامپیوتر کوچک. شما نمیتوانید مستقیماً داخلش را دستکاری کنید؛ فقط میتوانید به آن پیام بفرستید و خود شیء تصمیم میگیرد در پاسخ چه کند. این تصویر، ریشهی مستقیم مفهوم کپسولسازی (پنهانکردن دادهی درونی) است که در فصل چهارم مفصل سراغش میرویم.
این دو مکتب فکری، دو تیر بر یک کماناند و هر دو در پایتون امروز حضور دارند:
ویژگی
Simula (دال و نیگارد)
Smalltalk (آلن کی)
تمرکز اصلی
کلاس، نوع و ساختار داده
پیامرسانی میان اشیاء
هدف اولیه
شبیهسازی سیستمهای واقعی
محیط محاسباتی زنده و تعاملی
میراث در زبانها
C++، Java، C#
Ruby، Objective-C، Python
نگاه به شیء
نمونهای از یک قالب
موجودی مستقل و پاسخگو
نکتهی مهم این است که هیچکدام «غلط» نیستند. پایتون از هر دو وام گرفته: هم کلاس و نوع دارد (میراث Simula)، هم فلسفهی «به شیء بگو چهکار کند، خودت دستکاریاش نکن» را میپذیرد (میراث Smalltalk).
شیءگرایی کجای نقشهی پارادایمها ایستاده است؟
شیءگرایی تنها راه برنامهنویسی نیست؛ یکی از چند پارادایم است. پارادایم یعنی یک سبک بنیادی از فکرکردن به مسئله و ساختاردادن به راهحل. برای اینکه بدانید شیءگرایی چه چیزی را انتخاب میکند، باید بدانید در برابر چه چیزهایی ایستاده است.
بیایید یک مسئلهی واحد — محاسبهی مبلغ نهایی یک سفارش با تخفیف — را در چند پارادایم ببینیم.
پارادایم رویهای (Procedural): برنامه دنبالهای از دستورهاست. داده و رفتار جدا هستند؛ توابع داده را میگیرند، پردازش میکنند و برمیگردانند.
# procedural: data (the items list) and behavior (the function) are separatedeforder_total(items, discount): total =sum(item["price"] for item in items)return total * (1- discount)
پارادایم شیءگرا (Object-Oriented): داده و رفتار درون یک شیء کنار هم مینشینند و اشیاء با هم تعامل میکنند.
# object-oriented: data (self.items) and behavior (method) are packaged in one unitclassOrder:def__init__(self, items, discount):self.items = itemsself.discount = discountdeftotal(self): subtotal =sum(item.price for item inself.items)return subtotal * (1-self.discount)
پارادایم تابعی (Functional): کار ها بسیار جدا از هم و به صورت تخصصی توسط توابع انجام میشود. در این پارادایم، برای هر کاری یک تابع در نظر گرفته میشود.
from functools import reduce# تابع خالص برای محاسبه مجموع قیمتهاdefsum_prices(items):return reduce(lambdaacc, item: acc + item["price"], items, 0)# تابع خالص برای اعمال تخفیفdefapply_discount(total, discount):return total * (1- discount)# ترکیب توابعdeforder_total(items, discount):return apply_discount(sum_prices(items), discount)# استفادهitems = [{"price": 10}, {"price": 20}, {"price": 30}]print(order_total(items, 0.1)) # 54.0
حالا سوال کلیدی: کدام بهتر است؟ هیچکدام، بهطور مطلق. هر پارادایم برای دستهای از مسائل ساخته شده:
پارادایم
نقطهی قوت
نقطهی ضعف
رویهای
اسکریپتهای کوتاه، برنامهنویسی سیستمی
سازماندهی پروژههای بزرگ و پرداده
شیءگرا
سیستمهای بزرگ، فریمورکها، مدلسازی موجودیتها
محاسبات موازی، منطق کاملاً بیحالت
تابعی
پردازش داده، همروندی، محاسبات موازی
مدلسازی موجودیتهای پرحالت دنیای واقعی
پایتون چندپارادایمی است. همین که در مثال بالا توانستیم یک مسئله را هم رویهای، هم شیءگرا و هم تابعی بنویسیم، نشان میدهد پایتون شما را به یک سبک قفل نمیکند. این آزادی، هم قدرت است و هم مسئولیت: باید بدانید کی کدام را انتخاب کنید. همین انتخابها هستند که در سراسر این دوره با عنوان Tradeoff به آنها برمیگردیم — هیچ راهحلی مطلقاً درست نیست؛ همیشه گزینهها را میسنجیم و بهترین موقعیت را براساس شرایط برمیگزینیم.
منتقدان شیءگرایی چه میگویند؟
برخی از توانمندترین برنامهنویسان دنیا شیءگرایی را جدی نقد کردهاند. شنیدن این نقدها شما را برنامهنویس بهتری میکند.
۱. مدیریت State دشوار است. قلب شیءگرایی این است که اشیاء حالت (State) دارند و حالتشان در طول زمان تغییر میکند. وقتی دهها شیء همزمان حالت هم را تغییر میدهند، پاسخدادن به این پرسش که «همین الان دقیقاً وضعیت کل سیستم چیست؟» بهسرعت سخت میشود. این دقیقاً همان چیزی است که پارادایم تابعی، با حذف State تغییرپذیر، از آن فرار میکند.
به زبان سادهتر، فرض کنید یک تختهسیاه دارید که همه میتوانند چیزی روی آن بنویسند و پاک کنند (مدل شیءگرا). برای فهمیدن محتوای فعلی تخته، باید همه را زیر نظر داشته باشید. اما در مدل تابعی، هر بار که کسی میخواهد چیزی بنویسد، یک تختهٔ جدید با محتوای جدید به همه نشان میدهد و تختهٔ قبلی دیگر وجود ندارد. پس همیشه فقط یک تخته وجود دارد و فهمیدن محتوای آن، کار بسیار سادهای است.
۲. ارثبری شکننده است. جو آرمسترانگ، سازندهی زبان Erlang، جملهای مشهور دارد:
«مشکل زبانهای شیءگرا این است که همهی آن محیط ضمنی را با خودشان حمل میکنند. تو یک موز خواستی، اما چیزی که گرفتی یک گوریل بود که موز را در دست داشت — و کل جنگل هم پشت سرش آمد.»
جو آرمسترانگ، سازندهی زبان Erlang
منظور این است که وقتی از یک کلاس ارث میبرید، فقط آن یک قابلیتی را که میخواستید نمیگیرید؛ کل زنجیرهی وابستگیها و رفتارهای والدها را هم به ارث میبرید. سلسلهمراتب ارثبری عمیق (کلاسی از کلاسی از کلاسی…) کد را شکننده میکند: یک تغییر کوچک در والد، ممکن است فرزندانی را که اصلاً خبر نداشتید بشکند.
۳. کپسولسازی در پایتون واقعی نیست. در جاوا میتوانید یک فیلد را private کنید و زبان بهاجبار جلوی دسترسی بیرونی را میگیرد. در پایتون چنین قفلی وجود ندارد؛ پایتون فقط قرارداد دارد نه اجبار. این را در ادامهی فصل و بهطور کامل در فصل چهارم بررسی میکنیم.
۴. گاهی شیءگرایی فقط سربار اضافه است. خیلی از مسائل با یک تابع ساده تمیزتر حل میشوند. ساختن کلاس فقط به این دلیل که «باید شیءگرا باشیم» یک ضدالگوی رایج است و پیچیدگی بیدلیل تولید میکند.
آیا این نقدها یعنی شیءگرایی را کنار بگذاریم؟ نه. یعنی آن را هوشمندانه به کار ببریم — بدانیم کجا برازنده است و کجا نه. این تشخیص، همان چیزی است که در فصلهای بعد خواهید آموخت.
شیءگرایی در پایتون از کجا آمد؟ (خلاصه)
پایتون از همان ابتدا (نسخهی ۱.۰، سال ۱۹۹۴) شیءگرا بود، اما پیادهسازی اولیهاش کاستیهایی داشت که در نسخههای بعد برطرف شد. مهمترین تحول در نسخهی ۲.۲ رخ داد که مفهومی به نام New-Style Classes اضافه شد که در پایتون ۳ به یکی از بخش های اصلی شی گرایی در پایتون تبدیل شد.
نکتهی مهم: امروز که پایتون ۳ مینویسید، از میراث تمیز همهی این تصمیمها بهره میبرید و لازم نیست نگران این تاریخ باشید. class Foo: کافی و کامل است. فقط دو نکتهی عملی برایتان میماند: (۱) اگر روزی در کدی قدیمی به چیزی مثل class Foo(object): برخوردید، بدانید بازماندهی دوران پایتون ۲ است و در پایتون ۳ هیچ فرقی نمیکند؛ (۲) بهلطف همین تحول، امروز میتوانید از نوعهای توکار مثل list و dict هم ارث ببرید و نوعهای سفارشی خودتان را بسازید — که در فصلهای بعد بهکار میآید.
چرا اصلاً باید شیءگرایی یاد بگیریم؟
بگذارید صادق باشم! نه هر مسئلهای به شیءگرایی نیاز دارد، و نه شیءگرایی معجزه میکند. اما این پنج واقعیت را در نظر بگیرید:
۱. فریمورکهای اصلی بر پایهی آن ساخته شدهاند. جنگو، FastAPI، Flask، SQLAlchemy — همه شیءگرا هستند. یک مدل جنگو یک کلاس است. بدون یادگیری OOP، به سختی میتوان در کار با فریمورکها متبهر شد.
۲. دیزاین پترن ها به زبان OOP بیان میشوند. Factory، Strategy، Observer، Singleton — این الگوها، که راهحلهای آزمودهی مسائل تکرارشوندهاند، معمولا در قالب کلاس و شیء بیان میشوند.
۳. تستنویسی حرفهای به آن نیاز دارد. Mockها، Fixtureها و ابزارهای تست مدرن، عمیقاً بر مفاهیم شیءگرایی استوارند.
۴. معماری نرمافزار با آن نوشته میشود. Clean Architecture، معماری لایهای، و Domain-Driven Design — همهی این مفاهیم سطحبالا فرض میکنند شما شیءگرایی را میفهمید.
۵. بازار کار آن را میخواهد. کافی است چند آگهی استخدام پایتون را بخوانید؛ تقریباً همهشان تسلط بر OOP را شرط کردهاند. این تنها دلیل خوب یادگیری نیست، اما دلیلی واقعی است.