برنامه نویسی شی گرا (object oriented programming)، یک پارادایم برنامهنویسی است که بر پایهی مفهوم کلی «شی» استوار شده است. در پارادایم شی گرایی، هر چیزی را که فکرش را کنید، یک شی است. شی میتواند شامل ویژگیها و رفتارهایی باشد. ویژگیهای موجود در شی، در قالب فیلد (field) تعریف میشوند؛ رفتارها نیز در قالب متد (method) کدنویسی میشوند. متدهای موجود در یک شی، معمولا میتوانند به فیلدهای موجود در همان آبجکت دسترسی پیدا کنند یا حالت (state) آنها را تغییر دهند. اشیای مختلف موجود در یک برنامه، با یکدیگر تعامل دارند تا برنامه به درستی اجرا شود. در ادامه پس از بررسی پارادایم های برنامه نویسی، مفاهیم شی گرایی را به زبان ساده توضیح میدهیم و در آخر اصول برنامه نویسی شی گرا را شرح میدهیم.
پارادایم های برنامه نویسی
پارادایم های برنامه نویسی یکی از راههای دستهبندی و طبقهبندی زبانهای برنامه نویسی بر اساس ویژگیها و امکانات آنهاست. پارادایم برنامه نویسی در حقیقت یک متد و رویکرد به حل مسئله است؛ لذا میتوان با استفاده از یک زبان برنامه نویسی، با رویکردهای مختلف (پارادیمهای مختلف) برنامه نوشت. پارادایمهای برنامه نویسی را به طور کلی میتوان به دو دسته تقسیم کرد:
۱- برنامه نویسی دستوری (imperative programming)
۲- برنامه نویسی اخباری (declarative programming)
در ادامه هر یک را بررسی و زیرمجموعههای آنها را توضیح میدهیم.
۱-پارادایم برنامه نویسی دستوری (imperative programming)
این پارادایم، یکی از قدیمیترین متدها و رویکردهای حل مسئله است. در پارادایم برنامه نویسی دستوری، ما قدم به قدم و مرحله به مرحله به کامپیوتر میگوییم که «چگونه» برنامه ما را اجرا کند؛ برای درک موضوع به این مثال توجه کنید: فرض کنید بخواهیم به فردی بگوییم که برای ما پیتزا بخرد! اگر بخواهیم برنامهای با رویکرد دستوری برای وی بنویسیم، چنین برنامه میشود:
- از جایت بلند شو!
- لباس بپوش
- موهایت را شانه بزن
- کارت بانکی و سوییچ خودرو را بردار
- بیرون منزل برو
- سوار ماشین شو
- به پیتزافروشی برو
- پیتزا بخر
این یک برنامهی دستوری است. در واقع مرحله به مرحله، چگونگی اجرا را برای آن فرد شرح دادهایم. کد نویسی با رویکرد دستوری نیز این گونه است. در چنین برنامههایی، ما گام به گام به کامپیوتر میگوییم که حالت (state) متغیرها را چگونه تغییر دهد تا در نهایت به نتیجه مطلوب ما برسد و نتیجه مطلوب را ذخیره نماید.
مزایای برنامه نویسی دستوری:
- کدنویسی ساده
- تشکیلشده از متغیرها، حلقهها و …
معایب برنامه نویسی دستوری:
- مسئلههای بسیار پیچیده را نمیتوان با این متد حل کرد
- این متد خیلی بهینه نیست
- برنامه نویسی موازی امکانپذیر نیست
پارادایم برنامه نویسی دستوری را میتوان به سه دسته اصلی تقسیم کرد: (توجه کنید که یک کد، ممکن است به چند پارادایم تعلق داشته باشد)
برنامه نویسی ساختیافته (structured programming)
برنامه نویسی ساختیافته رویکردی مشابه رویکرد اصلی پارادایم برنامه نویسی دستوری است؛ نکته مهم در این نوع پارادایم آن است که پرش در برنامه با دستوراتی مانند GOTO، در برنامه نویسی ساختیافته وجود ندارد. بر خلاف رویکرد برنامه نویسی ساختیافته، در برنامه نویسی با ورژنهای قدیمی اسمبلی، میتوان از دستور GOTO استفاده کرد و برنامه در یک ساختار واحد و مشخص اجرا نمیشود. با استفاده از زبانهایی مانند C میتوان پارادایم ساختیافته را پیادهسازی کرد.
برنامه نویسی رویهای (procedural programming)
برنامه نویسی رویهای، رویکردی مشابه همان رویکرد اصلی پارادایم برنامه نویسی دستوری است؛ ایدهی جذاب و متفاوت برنامه نویسی رویهای آن است که قسمتهای مختلف برنامه را در قالب تابع تعریف میکند. این توابع به علت قابلیت استفاده مجدد، کار ما را راحت و برنامه را بهینهتر میکنند. توجه کنید که اگر در برنامه نویسی رویهای هم از GOTO استفاده نکنیم، رویکرد ساختیافته نیز داریم؛ اما اگر از پرش استفاده کنیم، برنامه ما دیگر ساختیافته نیست؛ هر چند رویهای هست. با استفاده از زبانهایی مانند C و ++C و java و Pascal میتوان پارادایم رویهای را پیادهسازی کرد.
برنامه نویسی شی گرا (object oriented programming)
در رویکرد شی گرایی، ما با یک سری اشیا در برنامه سروکار داریم که با یکدیگر در تعامل هستند. در این پارادایم، هر چیزی را در قالب یک شی میبینیم. امروزه، رویکرد شی گرایی، معروفترین و محبوبترین پارادایم در برنامه نویسی است. رویکردهای شی گرایی متفاوتی وجود دارند؛ محبوبترین آنها رویکرد کلاسپایه (class-based) است. مزایای شی گرایی عبارتند از:
- امنیت دادهها
- قابلیت استفاده مجدد از کد
- انعطاف بالا
- سطح انتزاع مناسب
با استفاده از زبانهایی مانند ++C و Java و Python میتوان برنامه نویسی شی گرا را پیاده سازی کرد.
تفاوت پارادایم رویهای و شی گرایی به زبان ساده
در برنامه نویسی رویهای، برنامه به توابع کوچکتر شکسته میشود؛ اما در برنامه نویسی شی گرا، برنامه به اشیا کوچکتر شکسته میشود. در برنامه نویسی شی گرا، بر خلاف رویکرد رویهای، میتوان برای کلاسها سطح دسترسیهایی به شکل private یا public یا protected تعریف کرد. به دلیل آن که مخفی کردن دادهها در برنامه نویسی شی گرا ممکن است، این رویکرد، امنیت بالاتری دارد.
۲-پارادایم برنامه نویسی اخباری (declarative programming)
در این پارادایم تنها بر روی نتیجه مد نظر و ویژگیهای آن تمرکز داریم. در حقیقت به جای آن که چگونگی و مراحل رسیدن به نتیجه را شرح دهیم، ویژگیها و «چیستی» خروجی برنامه را توضیح میدهیم. برنامه نویسی اخباری بدون آن که توضیحی در مورد جریان کنترل مرحله به مرحله برنامه دهد، منطق محاسباتی برنامه را توضیح میدهد. تاکید پارادایم برنامه نویسی اخباری بر روی آن چیزی است که باید حاصل شود نه بر روی مراحل رسیدن به خواسته مورد نظر.
پارادایم برنامه نویسی اخباری را میتوان به سه دسته اصلی تقسیم کرد:
برنامه نویسی منطقی (logic programming)
این پارادایم بر اساس منطق ریاضیاتی به وجود آمده است. در برنامه نویسی منطقی، ما یک دانش اولیه داریم؛ برنامه با استفاده از این دانش اولیه، به سوالات ما پاسخ میدهد و به این ترتیب میتوانیم مسائل را حل کنیم. در هوش مصنوعی و یادگیری ماشین نیز ما از رویکردی مشابه با پارادایم برنامه نویسی منطقی برای حل مسئله استفاده میکنیم. با استفاده از بعضی زبانها مانند Prolog، میتوان پارادایم برنامه نویسی منطقی را پیادهسازی کرد.
برنامه نویسی تابعی (functional programming)
پارادایم برنامه نویسی تابعی، ریشه ریاضیاتی دارد؛ در این پارادایم، برنامه از اجرای یک سری توابع ریاضیاتی خالص (pure) تشکیل میشود. برنامه نویسی تابعی یک سری ویژگیها دارد:
- توابع موجود کاملا مستقل هستند و هیچ اثر جانبی بر روی سایر اجزای برنامه ندارند
- توابع موجود به ازای ورودی مشابه، خروجی مشابهی دارند
- در برنامه نویسی تابعی از حلقهها استفاده نمیشود و در صورت نیاز باید از برنامه نویسی بازگشتی استفاده کنیم
- از assignment استفاده نمیکنیم و متغیرها در طول برنامه تغییر نمیکنند
- توابع ما یک first-class variable هستند؛ به این معنا که میتوان آنها به به عنوان یک ورودی به توابع دیگر داد
با استفاده از زبانهایی مانند Perl و Javascript و Scala میتوان پارادایم برنامه نویسی تابعی را پیادهسازی کرد.
برنامه نویسی داده محور یا دیتابیس (database / data-driven programming)
این پارادایم، نوعی برنامه نویسی برای کار با دادههای موجود در دیتابیس و تغییرات آنهاست. زبانهایی مانند SQL، از رویکرد داده محور پیروی میکنند.
آشنایی بیشتر با شی گرایی به زبان ساده
همانطور که در قسمتهای قبلی گفته شد، پارادایم برنامه نویسی شی گرا، محبوبترین پارادایم در بین برنامهنویسان است؛ از این رو در ادامه به توضیح بیشتر مفاهیم برنامه نویسی شی گرا میپردازیم. وقتی میخواهیم با رویکرد شی گرایی برنامه نویسی کنیم، مانند آن است که بخواهیم یک تکه از جهان را در کامپیوتر خود بسازیم. اشیای برنامه نویسی در واقع مدلهای کامپیوتری اشیا دنیای واقعی هستند. اشیا میتوانند هر چیزی باشند؛ اگر یک پردازشگر متن مینویسید، اشیا کلمات یا پاراگرافها هستند؛ اگر یک شبکه اجتماعی میسازید، اشیا آدمها و پیامها هستند و اگر بازی رایانهای مینویسید، اشیا هیولاها هستند!
مفهوم کلاس و شی در برنامه نویسی شی گرا
ما میتوانیم اشیا را دستهبندی کنیم؛ به یک دسته از اشیا که ویژگیهای یکسانی دارند، کلاس گفته میشود. کلاس در واقع یک قالب کلی است؛ اما هر شی ویژگیهای یکتای خودش را دارد. میتوان از یک کلاس به تعداد محدود شی ساخت که به هر یک از این اشیا، یک instance یا نمونه از آن کلاس میگویند. توجه کنید که با نوشتن کلاس، هیچ حافظهای اشغال نمیشود و حافظه زمانی تخصیص مییابد که یک شی بسازیم؛ در این صورت آن شی یک بخش از حافظه را اشغال میکند. به عنوان مثال، کلاس Student را در نظر بگیرید که بیانگر ویژگیهای کلی دانشجویان مانند نام، شماره دانشجویی، کد ملی و … است. حال اگر یک نمونه از این کلاس بسازیم، به آن نمونه شی گفته میشود؛ شی دیگر کلی نیست و نام، شماره دانشجویی، کد ملی و … مختص به خود را دارد. به زبانی دیگر، هر شی دارای هویت (identity) یکتا و خاص خود است.
هویت (identity) شی چیست؟
وقتی یک instance از کلاس (شی) میسازیم، یک بخشی از حافظه برای نگهداری این شی اشغال میشود؛ برای آن که بتوان به این شی در حافظه دسترسی داشت، یک آدرس یکتا به شی تعلق میگیرد و هر شی جدید هم آدرس مختص خود را دارد. هویت یک شی در واقع یک ویژگی از شی است که آن را کاملا از بقیه اشیا متمایز میکند. معمولا آدرس شی در حافظه را به عنوان هویت (identity) شی در نظر میگیریم. به عنوان مثال، اگر شی ما یک دانشجو باشد، یک کد یکتا که بیانگر دقیقا آن دانشجوست میتواند هویت شی باشد.
رفتار کلاس یا متد در شی گرایی
هر شیئی که ما بسازیم، یک سری رفتارهایی دارد؛ به عنوان مثال فرض کنید یک شی دایره ساختهایم؛ این دایره میتواند در صفحه حرکت کند و به بالا یا راست یا … برود. حرکت دایره به راست یک رفتار است و میتوان در قالب یک متد (method) آن را تعریف کرد.
هر متد کلاس یک سری پارامتر را به عنوان ورودی میگیرد؛ این پارامترها نحوهی اجرای متد را توضیح میدهند. مثال دایره را در نظر بگیرید؛ میتوان به عنوان پارامتر به شی گفت که ۴۰ پیکسل به بالا حرکت کند (۴۰ یک پارامتر برای متد حرکت به بالاست). اگر متد را برابر یک فعل در نظر بگیریم، پارامتر یک قید محسوب میشود.
ویژگیهای شی و تعریف field
هر شی یک سری ویژگیها دارد؛ در مثال دایره، ویژگیهایی مانند رنگ، شعاع، مکان در صفحه و … را میتوان به عنوان ویژگی در نظر گرفت. ویژگیهای شی را در قالب field تعریف میکنیم؛ یعنی هر یک از موارد رنگ، شعاع و … یک فیلد به حساب میآیند. کلاس تعیین میکند که اشیای موجود در داخل آن چه فیلدهایی داشته باشند.
مفهوم حالت (state) در برنامه نویسی شی گرا
هر شی یک سری مقدار برای هر فیلد دارد؛ به عنوان مثال یک شی دایره ممکن است مقادیر زیر را برای هر فیلد داشته باشد:
شعاع = ۱۰ سانتی متر
رنگ = قرمز
به این مقادیر (۱۰ سانتی متر و قرمز)، حالت (state) شی میگویند. حالت شی ممکن است در طول برنامه تغییر کند. (با استفاده از متدهای شی میتوان حالت آن را تغییر داد)
اصول برنامه نویسی شی گرا
به طور کلی، ۴ اصل برای ساخت کلاس در برنامه نویسی شی گرا وجود دارد:
- کپسولهسازی (encapsulation)
- انتزاع (abstraction)
- وراثت (inheritance)
- چندریختی (polymorphism)
این لغات ترسناک به نظر میرسند! اما نگران نباشید؛ در ادامه سعی میکنیم به سادهترین نحو آنها را توضیح دهیم.
اصل کپسولهسازی (encapsulation) در شی گرایی
فرض کنیم یک برنامهای داریم که از اشیای مختلف تشکیل شده است و این اشیا با توجه به قوانین برنامه در حال تعامل با یکدیگر هستند. ما موقعی اصل کپسولهسازی را رعایت کردهایم که هر شی حالت (state) فیلدهای خود را به صورت private حفظ کند؛ یعنی به سایر اشیای موجود در برنامه، اجازهی دسترسی مستقیم و تغییر حالت را ندهد. در این حالت دسترسی تنها از طریق متدهای public موجود در شی امکانپذیر است.
فرض کنید یک کلاس Cat ساختهایم. این کلاس سه فیلد mood و hungry و energy را دارد که هر سه private هستند. همچنین یک متد private با نام meow دارد! تا این جای کار، کلاس ما کاملا کپسولهشده است و به هیچ وجه نمیتوان از خارج کلاس به فیلدها و متد آن دسترسی داشت. ما تا جایی که بتوانیم باید کلاس را کپسوله کنیم؛ اما طبیعتا انجام این کار به شکل صد درصدی امکانپذیر نیست. برای آن که از خارج از کلاس بتوان به کلاس Cat دسترسی داشت، سه متد public با نامهای feed و play و sleep تعریف میکنیم؛ از خارج کلاس Cat میتوان به این سه متد دسترسی داشت؛ هر متد حالت برنامه را تغییر میدهد و بعضی از آنها نیز متد meow را صدا میزنند. برای فهم بهتر به نمودار زیر توجه کنید:
اصل انتزاع (abstraction) در شی گرایی
انتزاع یک اصل تکمیلکننده برای اصل کپسولهسازی است. در اصل کپسولهسازی گفتیم که تا حد امکان باید متدها و فیلدها را به صورت private تعریف کنیم و اجازه دسترسی از بیرون کلاس به درون کلاس را ندهیم؛ اما به علت تعاملی که باید بین کلاسها وجود داشته باشد، بالاخره باید یک سری متد private تعریف کنیم. اصل انتزاع بیان میکند که اجازه دسترسی از خارج، فقط و فقط باید برای متدهایی باشد که مفهوم سطح بالایی دارند؛ برای درک مفهوم سطح بالا مثالی میزنیم:
فرض کنید میخواهید از یک ماشین خودکار، قهوه بخرید؛ برای انجام این کار کافی است چند دکمه بزنید و پس از آن لیوان قهوه را بردارید؛ اما در داخل دستگاه، اتفاقات خیلی زیادتر و پیچیدهتری میافتد. شما از تمام این جزئیات ریز چشمپوشی کردهاید و با چند مفهوم سطح بالا یعنی برداشتن لیوان و کلیک دکمهها سر و کار داشتهاید. در برنامههایی که مینویسیم نیز باید اصل انتزاع را رعایت کنیم. یعنی کلاسها تنها از طریق مفاهیم سطح بالا به خارج از خود دسترسی داشته باشند.
در نرمافزارهای امروزی، معمولا با تعداد کلاسهای خیلی زیاد و کدهای حجیم سروکار داریم که به شدت با یکدیگر در تعامل هستند. اگر اصول کپسولهسازی و انتزاع را در نوشتن چنین کدها و کلاسهایی رعایت نکنیم، نگهداری کد بسیار سخت و پس از مدتی غیرممکن میشود. فرض کنید بخواهید یک تکه از یک کلاس را تغییر دهید؛ اگر انتزاع را رعایت نکرده باشید، احتمال نابودی برنامه شما و ممکن نبودن نجات آن بسیار بالاست.
اصل وراثت (inheritance) در شی گرایی
تا این جای کار یاد گرفتیم که چگونه میتوان با استفاده از پارادایم برنامه نویسی شی گرا، یک برنامه حجیم را توسعه داد و نگهداری کرد. یکی دیگر از مشکلاتی که در شی گرایی وجود دارد آن است که کلاسهای ما معمولا شباهتهای زیادی به یکدیگر دارند؛ مثلا فرض کنید یک کلاس انسان و یک کلاس دانشجو داشته باشیم؛ هر دوی این کلاسها موارد مشابهی چون نام، سن، ایمیل و … دارند. اگر بخواهیم برای انسان و دانشجو کلاسهای مستقل و جداگانهای ایجاد کنیم، علاوه بر حجیم شدن برنامه و غیربهینهبودن آن، با مشکلات و پیچیدگیهای مختلفی روبهرو میشویم. راه حل، استفاده از اصل وراثت است. اصل وراثت بیان میکند که یک کلاس میتواند یک سری ویژگیهایش را از کلاسی دیگر به ارث ببرد. به عنوان مثال، کلاس دانشجو میتواند ویژگیهای کلاس انسان را نیز به ارث ببرد. به کلاس انسان، کلاس والد (parent) و به کلاس دانشجو، کلاس فرزند (child) گفته میشود. به مثال زیر توجه کنید:
اصل چندریختی (polymorphism) در شی گرایی
همان طور که در قسمت قبلی گفتیم، اصل وراثت به ما کمک میکند که متدها و فیلدها را از یک کلاس دیگر به ارث ببریم. چندریختی ما را قادر میکند تا از این متدها برای انجام کارهای مختلف استفاده کنیم. فرض کنید یک کلاس با نام Animal داریم. داخل این کلاس یک متد با نام animalSound وجود دارد. زیرکلاسهای کلاس Animal میتوانند حیوانهای مختلف مانند Cat و Dog باشند. هر یک از این زیرکلاسها نیز متد animalSound خاص خود را دارند. در این صورت هر موقع animalSound را برای یک شی فرامیخوانیم، صدای مربوط به آن شی اجرا میشود.
عالی بود
ممنون از شما
با سلام.
خیلی جامع و ساده و قابل فهم ارایه شده.
بسیار ممنون جناب مهندس کریمی.