ما طراحی میکنیم تا علاوه بر نیازهای عملیاتی، به نیازهای غیر عملیاتی (Non Functional Requirements) نیز فکر کنیم؛ در حالیکه در زمان برنامه نویسی صرفا به Functionality فکر میکنیم.
کتاب Object Oriented Design Heuristics اولین کتاب در زمینه طراحی و توسعه شیء گرا میباشد. خواندن آن برای برنامه نویسان در هر رده ای که هستند، مفید خواهد بود و میتوانند از این Heuristicها (قواعد شهودی) به عنوان ابزاری برای تبدیل شدن به یک توسعه دهنده برتر، استفاده کنند.
در این کتاب بیشتر، بهبود طراحی شیء گرا هدف قرار داده شدهاست و در این راستا بیش از 60 دستورالعمل که هیچ وابستگی به زبان خاصی هم ندارند، ارائه شده است. قواعد شهودی در واقع قوانین سخت گیرانهای نیستند. بلکه میتوان آنها را به عنوان یک مکانیزم هشدار در نظر گرفت که در زمان نیاز حتی میتوان آنها را نقض کرد.
پیشنهاد میکنم حداقل برای اینکه ادبیات فنی خود را سامان ببخشید و با ادبیات یکسانی باهم صحبت کنیم، این کتاب را مطالعه کنید.
Introduction to Classes and Objects
پارادایم شیء گرا از مفاهیم کلاس و آبجکت، به عنوان بلوکهای ساختاری پایهای در شکل گیری یک مدل سازگار و استوار برای تحلیل، طراحی و پیاده سازی نرم افزار، استفاده میکند.
این مفاهیم را با یک مثال واقعی، بهتر میتوان شرح داد. یک اتاق پر از جمعیت را درنظر بگیرید؛ اگر شما میپرسیدید «چه تعداد از حاضرین در این اتاق میتوانند یک ساعت زنگدار(alarm clock ) را با در دست داشتن تمام قطعات آن، بسازند؟» در بهترین حالت یک یا دو نفر تمایل داشتند دست خود را بالا ببرند. اگر در همین اتاق میپرسیدید، «چه تعداد از حاضرین در این اتاق میتوانند یک ساعت زنگدار را برای ساعت 9 صبح تنظیم کنند؟» بدون شک بیشتر جمعیت تمایل داشتند دست خود را بالا ببرند.
آیا نامعقول نیست که این تعداد جمعیت زیاد، ادعا دارند که میتوانند از ساعت زنگدار استفاده کنند، درحالیکه حتی نمیتوانند یک ساعت زنگدار بسازند؟ پاسخ بی درنگ برای این سوال «البته که نه! سوال شما نامعقول است» میباشد.
در دنیای واقعی خیلی چیزها هستند که ما میتوانیم از آنها استفاده کنیم، بدون آنکه دانشی درباره پیاده سازی آنها داشته باشیم؛ مانند: یخچالها، اتومبیلها، دستگاههای فتوکپی، کامپیوترها و غیره. چون آنها برای استفاده شدن از طریق واسط عمومی خودشان، تعریف و طراحی شدهاند. لذا حتی بدون داشتن دانشی از پیاده سازی آنها، استفاده از آنها آسان میباشد. این واسط عمومی وابسته به دستگاه مورد نظر است. اما جزئیات پیاده سازی دستگاه را از دید کاربرانش پنهان میکند. این استراتژی طراحی، چیزی است که به سازنده اجازه میدهد بدون آنکه کاربران رنجیده شوند، با آزادی عمل، 60 مؤلفه کوچک استفاده شده در ساخت ساعت زنگدار را تعویض کند.
مثال دیگری از واسط عمومی در مقابل جزئیات پیاده سازی، میتواند در حوزه اتومبیلها دیده شود. زمانیکه تولید کنندگان اتومبیل از سیستمهای احتراق مکانیکی به سمت سیستمهای احتراق الکترونیکی کوچ کردند، تعداد خیلی کمی از کاربران اتومبیلها نگران این موضوع بودند. اما چرا؟ چون واسط عمومی آنها مانند سابق ماند و تنها پیاده سازی تغییر کرد. فرض کنید که شما به قصد خرید اتومبیل به یک فروشنده اتومبیل مراجعه میکنید و فروشنده یک سوئیچ را به شما داده و از شما میخواهد برای تست آن را برانید. بعد از تلاشی که برای استارت زدن داشتید، فروشنده اعلام میکند که در این مدل برای استارت زدن باید ابتدا کاپوت را بالا زده و دکمه قرمز را فشار دهید. در این حالت، بدلیل اینکه واسط عمومی اتومبیل دستخوش تغییر بوده است، باعث ناراحتی شما خواهد شد.
این فلسفه، دقیقا یکی از ایدههای پایهای در پارادایم شیءگرا میباشد. تمام جزئیات پیاده سازی در سیستم شما باید در پشت یک واسط عمومی مستحکم و سازگار، از کاربران آنها پنهان باشد. نیاز کاربران، دانستن درباره واسط عمومی میباشد؛ اما هرگز مجاز به دیدن جزئیات پیاده سازی آنها نیستند. با این روش، پیاده ساز میتواند به هرشکلی که مناسب است، پیاده سازی را تغییر دهد؛ درحالیکه واسط عمومی مانند سابق میباشد. به عنوان مسافری که مکرر سفر میکنم، به شما اطمینان میدهم که استفاده از ساعتهای زنگدار با وجود عدم اطلاع از پیاده سازی آنها، فواید عظیمی دارند. در هتلهای زیادی که از دسته بندیهای گستردهای از ساعتها مانند الکتریکی، قابل کوک (windup)، باتری خور، در هر دو مدل دیجیتال و آنالوگ استفاده میکنند، اقامت کردهام. یکبار هم اتفاق نیفتادهاست در حالیکه در هواپیما نشسته باشم، نگران این باشم که قادر نخواهم بود از ساعت زنگی اتاقم در هتل استفاده کنم.
بیشتر خوانندگان این کتاب، با وجود اینکه در نزدیکی آنها شاید ساعت زنگداری هم نباشد، ولی منظور بنده را با عبارت «ساعت زنگدار» متوجه شدند. به چه دلیل؟ شما در زندگی خودتان ساعتهای زنگدار زیادی را میبینید و متوجه میشوید که همه آنها از یکسری خصوصیات مشترک مانند زمان، یک زمان هشدار و طراحیای که مشخص میکند هشدار روشن یا خاموش است، بهره میبرند. همچنین متوجه میشوید که همه ساعتهای زنگداری که دیدهاید امکان تنظیم کردن زمان، تنظیم زمان هشدار و روشن و خاموش کردن هشدار را به شما میدهند. در نتیجه، شما الان مفهومی را به نام «ساعت زنگدار» دارید که مفهومی را از داده و رفتار، در یک بسته بندی مرتب برای همه ساعتهای زنگدار، تسخیر میکند. این مفهوم به عنوان یک Class (کلاس) شناخته میشود. یک ساعت زنگدار فیزیکی که شما در دست خود آن را نگه داشتهاید، یک Object (وهله، Instance) ای از کلاس ساعت زنگدار میباشد. رابطه بین مفهوم کلاس و وهله، Instantiation Relationship (وهله سازی) نام دارد. به یک object، ساعت زنگدار وهله سازی شده (Instantiated) از کلاس ساعت زنگدار گفته میشود؛ در حالیکه از کلاس ساعت زنگدار به عنوان تعمیم (Generalization) از همه objectهای کلاس ساعت زنگدار که شما با آنها روبرو شدهاید، یاد میشود.
اگر من به شما میگفتم که ساعت زنگدارم از روی پاتختی (میز کوچک کنار تخت که دارای کشو میباشد) من پرید، من را گاز گرفت، سپس گربهی همسایه را دنبال کرد، قطعا مرا دیوانه به حساب میآوردید. اگر به شما میگفتم که سگ من کارهای مشابهای را انجام میدهد، کاملا منطقی میبود. چون نام یک کلاس تنها به مجموعهای از خواص اشاره نمیکند، بلکه رفتارهای موجودیت (entity) را نیز مشخص میکند. این رابطه دوسویه بین داده و رفتار، اساس پارادایم شیء گرا میباشد.
- هویت خود (ممکن است آدرس آن در حافظه باشد) - its own identity
- خواص کلاس خود (معمولا استاتیک) و مقادیر این خواص (معمولا پویا) - attributes of its class
- رفتار کلاس خود (از دید پیاده ساز) - behavior of its class
- واسط منتشر شده کلاس خود (از دید استفاده کننده) - published interface of its class
یک کلاس را می توان با record definition (ساختار داده پایه، struct) و لیستی از عملیاتی که مجاز به کار بر روی این record definition هستند، پیاده سازی کرد. در زبانهای رویهای (Procedural) یافتن وابستگی دادهها در یک تابع معین، آسان میباشد. این کار را میتوان به سادگی با بررسی کردن جزئیات پیاده سازی تابع و مشاهده نوع داده پارامترهای آن، مقادیر بازگشتی و متغییرهای محلیای که تعریف شدهاند، انجام داد. اگر قصد شما پیدا کردن وابستگیهای تابعی بر روی یک داده میباشد، باید همه کد را بررسی کرده و به دنبال توابعی باشید که به داده شما وابسته هستند. در مدل شیء گرا، هر دو نوع وابستگی (داده به رفتار و رفتار به داده) به راحتی در دسترس میباشند. وهلهها، متغیرهایی از یک نوع داده کلاس هستند. جزئیات داخلی آنها باید فقط برای لیست توابع مرتبط با کلاسهایشان آشکار باشد. این محدودیت دسترسی به جزئیات داخلی وهلهها، Information Hiding نامیده میشود. اختیاری بودن این بحث در خیلی از زبانهای شیء گرا ما را به سمت اولین قاعده شهودی هدایت میکند.
همه دادهها باید در داخل کلاس خود پنهان شده باشند. (All data should be hidden within its class)
با نقض این قاعده، امکان نگهداری را هم از دست میدهید. اجبار به پنهان کردن اطلاعات در مراحل طراحی و پیاده سازی، بخش عظیمی از فواید پارادایم شیء گرا میباشد. اگر داده به صورت عمومی تعریف شده باشد، تشخیص اینکه کدام بخش از عملیات (functionality) سیستم به آن داده وابسته است، سخت و مشکل خواهد بود. در واقع، نگاشت تغییرات داده به عملیات سیستم، همانند طراحی و پیاده سازی در دنیای action-oriented میباشد. ما مجبور میشویم برای تشخیص اینکه کدام عملیات به داده مورد نظر ما وابسته است، تمام عملیات سیستم را بررسی کنیم، تا به این ترتیب متوجه شویم.
برخی اوقات، یک توسعه دهنده استدلال میکند «نیاز دارم این بخش از داده را عمومی تعریف کنم زیرا ....» در این وضعیت، توسعه دهنده باید از خود سوال کند «کاری که تلاش دارم با این داده انجام دهم چیست و چرا کلاس این عملیات را خودش برای من انجام نمیدهد؟» در همه موارد این کلاس است که به سادگی عملیات ضروری را فراموش کردهاست. کمی بر روی شکل 2.2 فکر کنید. توسعه دهنده به صورت تصادفی فکر کرده است که عضو byte_offset را برای مجاز ساختن دسترسی تصادفی I/O، به صورت عمومی تعریف کند. اما چیزی که واقعا برای انجام این کار به آن نیاز داشت، تعریف یک operation بود (در زبان سی، توابع fseek و ftell برای ممکن کردن دسترسی تصادفی I/O، موجود هستند).
مراقب توسعه دهندههایی که جسورانه میگویند: «ما میتوانیم این بخش از داده را تغییر دهیم، زیرا هیچوقت تغییر نخواهد کرد!» باشید. طبق قانون برنامه نویسی مورفی، اولین بخشی که نیاز به تغییر خواهد داشت همین بخش از داده است.
به عنوان مثال دیگر برای روشنتر شدن بحث، کلاس Point را که پیاده سازی آن به روش مختصات دکارتی میباشد، در نظر بگیرید. یک طراح بیتجربه ممکن است دلیل تراشی کند که ما میتوانیم دادههای X و Y را به صورت عمومی تعریف کنیم؛ چرا که هیچ موقع تغییر نخواهند کرد. فرض کنید نیاز جدیدی مبنی بر اینکه پیاده سازی Point به ناچار باید از دکارتی به قطبی تغییر کند، به دست شما برسد. به این صورت استفاده کنندگان از این کلاس Point نیز باید تغییر کنند. حال اگر دادهها پنهان بودند و عمومی نبودند، در نتیجه فقط لازم بود پیاده سازهای این کلاس، کد خود را تغییر دهند.