مفاهیم برنامه‌نویسی شی‌گرا به زبان ساده

مفاهیم برنامه‌نویسی شی‌گرا به زبان ساده

آیا تا به حال دقت کرده‌اید که در مصاحبه‌‌های شغلی چه پرسش‌های تکراری و کلیشه‌ای پرسیده می‌شود؟ بدون شک می‌دانید منظورم چیست.

برای مثال:

پنج سال دیگر خودت را کجا می‌بینی؟

یا حتی از آن هم بدتر:

فکر می‌کنی بزرگ‌ترین نقطه‌ضعف تو چیست؟

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

در این مقاله می‌خواهم در مورد یک نوع پرسش مشابه در دنیای برنامه‌نویسی صحبت کنم:

اصول اساسی برنامه‌نویسی شیء‌گرا چیست؟

این پرسش پرتکراری در مصاحبه‌های شغلی برنامه‌نویسی است و نمی‌توانید آن را نادیده بگیرید. وقتی به دنبال کار می‌گشتم این پرسش از من هم پرسیده شده و بعدها خودِ من هم در مصا‌حبه‌ها از متقاضیان کار آن را پرسیده‌ام.

بیشتر توسعه‌دهندگان تازه‌کار و کم‌تجربه باید به این پرسش پاسخ‌ بدهند. زیرا پاسخ به این پرسش سه نکته را برای مصاحبه‌کنندگان آشکار می‌کند:

۱- آیا داوطلب از قبل برای مصاحبه آماده شده‌ است؟

به طور معمول اگر بی‌درنگ پاسخ پرسش مطرح شده را بدهید، امتیاز بیشتری می‌گیرید.

۲- آیا داوطلب آموزش‌های لازم را دیده است؟

این‌ که بتوانید اصول برنامه‌نویسی شیء‌گرا را بگویید، نشان می‌دهد که شما پیش از این آموزش‌های لازم را دیده‌اید، از مرحله‌ی paste ـ copy عبور کرده و به مسائل از چشم‌انداز بالاتری نگاه می‌کنید.

۳- درک داوطلب سطحی است یا عمیق؟

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

چهار اصل مهم برنامه نویسی شیء‌گرا (OOP) عبارتند از: کپسوله کردن (encapsulation)، انتزاع (abstraction)، ارث بری (inheritance) و چند ریختی (polymorphism).

برای برنامه نویسان کم‌تجربه شاید این واژه‌ها، کمی ترسناک به نظر برسند و توضیحات پیچیده و بیش‌ از حد طولانی ویکی‌پدیا هم ممکن است سبب سردرگمی بیشتر شود.

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

(encapsulation)کپسوله‌کردن

فرض کنید یک برنامه داریم و همچنین فرض کنید که بر اساس قوانین تعریف شده‌، در این برنامه چند شیء‌ وجود دارند که از نظر منطق با هم متفاوتند.

کپسوله‌کردن زمانی تحقق پیدا می‌کند که هر یک از این شی‌ها حالت خصوصی (private) خود را در داخل کلاس حفظ کند و دیگر شی‌ها دسترسی مستقیم به آن‌ نداشته باشند. در عوض هر یک از آن‌ها بتوانند فهرستی از توابع عمومی (public) را (در قالب متدها) فراخوانی کنند.

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

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

می‌توانید به گربه غذا بدهید اما نمی‌توانید به صورت مستقیم میزان گرسنگی گربه را تغییر دهید

در این‌جا متغییرهای خصوصی mood ،hungry و energy حالت گربه را تعیین می‌کنند. همچنین کلاس Cat یک متد خصوصی به نام ()meow دارد که هر گاه بخواهد می‌تواند آن را فراخوانی کند. سایر کلاس‌ها نمی‌توانند به گربه بگویند چه زمانی میومیو کند (متد ()meow را فراخوانی کند).

تاثیری که دیگر کلاس‌ها می‌توانند روی گربه داشته باشند در قالب متدهای عمومی ()play() ،sleep و ()feed تعریف شده است. آن‌ها می‌توانند با استفاده از این متدها به نحوی حالت گربه را تغییر دهند که او میومیو کند. به این ترتیب، ارتباط میان متغییرهای خصوصی کلاس Cat  و متدهای عمومی آن برقرار می‌شود. کپسوله کردن همین است!

 (abstraction) انتزاع 

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

هدف مفهوم انتزاع، آسان کردن این مشکل است.

انتزاع به این معنی است که برای استفاده از هر شیء‌ تنها ساز و کار سطح بالای آن نشان داده شود. این ساز و کار باید جزئیات پیاده سازی داخلی را پنهان کند و تنها عملیات مرتبط با سایر شیء‌ها را نشان دهد.

استفاده از این ساز و کار باید آسان باشد و در طول زمان به ندرت دچار تغییر شود. این ساز و کار را می‌توان مجموعه‌‌ی کوچکی از متدهای عمومی در نظر گرفت که هر کلاس دیگری بدون این‌که بداند چگونه کار می‌کنند، بتواند از آن‌ها استفاده کند. 

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

مثالی دیگر از انتزاع در زندگی واقعی، استفاده از گوشی تلفن همراه است.

گوشی تلفن همراه پیچیده است اما استفاده از آن ساده است

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

 

 (inheritance) ارث بری

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

اما آیا می‌دانید یکی دیگر از مشکلات رایج در طراحی OOP چیست؟ این که شی‌ها اغلب بسیار شبیه به هم هستند. همه‌ی آن‌ها یک منطق مشترک دارند اما به طور کامل شبیه به هم نیستند.

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

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

کلاس فرزند از همه‌ی فیلدها و متدهای کلاس والد استفاده می‌کند (بخش مشترک) و علاوه بر آن، می‌تواند فیلدها و متدهای مخصوص به خود را نیز داشته باشد (بخش غیر مشترک).

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

یک معلم خصوصی نوعی معلم است و معلم، نوعی شخص است

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

 

 (polymorphism) چند ریختی

در زبان یونانی کلمه‌ی polymorphism یا چند ریختی به معنی اشکال متعدد است.

ما با قدرت ارث‌ بری آشنا هستیم و خوش‌بختانه از آن استفاده می‌کنیم. اما مشکل دیگری وجود دارد. فرض کنید یک کلاس والد داریم و چندین کلاس فرزند که از آن ارث‌ می‌برند. گاهی لازم است که از مجموعه‌ای ـ برای مثال یک لیست ـ شامل تمام این کلاس‌ها استفاده کنیم یا ممکن است بخواهیم از متدی که در کلاس والد به کار برده‌ایم در کلاس‌های فرزند نیز استفاده کنیم.

این موضوع را می‌توان با استفاده از چند ریختی حل کرد.

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

این کار اغلب با تعریف یک رابط (والد) برای استفاده‌های مجدد، انجام می‌شود. در این رابط مجموعه‌ای از متدهای مشترک تعریف می‌شود. سپس هر یک از کلاس‌های فرزند نسخه‌ی ویژه‌ی خود از این متدها را پیاده‌سازی می‌کند.

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

به طرح پیاده‌سازی شکل‌های هندسی که در ادامه آمده است، نگاه کنید. آن‌ها از یک رابط مشترک به منظور محاسبه‌ی محیط و مساحت بهره می‌برند:

اکنون مثلث، دایره و مستطیل می‌توانند در یک مجموعه واحد قرار بگیرند

ارث‌ بری هر سه این شکل‌ها از رابط شکل، این امکان را فراهم می‌آورد که بتوانید لیستی متشکل از مثلث، دایره و مستطیل ایجاد کنید و با همه‌ی آن‌ها مانند یک شیء‌ یکسان ـ و نه سه شیء‌ مختلف ـ رفتار کنید. 

اگر قرار باشد مساحت یکی از عناصر این لیست محاسبه شود، متد مناسب همان عنصر، پیدا و اجرا می‌شود. اگر عنصر مورد نظر یک مثلث باشد، متد ()CalculateSurface از کلاس triangle اجرا می‌شود. همچنین اگر این عنصر یک دایره باشد، متد ()CalculateSurface اختصاصی شده در کلاس circle فراخوانی می‌شود به همین‌ترتیب، در سایر موارد نیز متد متناسب با هر عنصر فراخوانی خواهد شد. 

اگر تابعی دارید که با داشتن یک مقدار (برای مثال طول ضلع یا قطر و…) عمل می‌کند، نیازی نیست برای سه شکل مثلث، دایره و مستطیل سه بار آن را تعریف کنید. می‌توانید آن را تنها یک‌ بار تعریف کرده و شکل را به عنوان آرگومان بپذیرید. به این ترتیب دیگر مهم نیست شکل دایره باشد یا مثلث یا مستطیل؛ چون به هر حال قرار است برای همه‌ی این شکل‌ها یک متد واحد ـ برای مثال متد ()CalculatePriameter، اجرا شود.

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

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

منبع

نظرات
اگر login نکردی برامون ایمیلت رو بنویس: