من چند دهه است که با زبان های شی گرا برنامه نویسی می کنم. اولین زبان شی گرایی که استفاده کردم C ++ و سپس Smalltalk و در آخر. NET و Java بود. خیلی مشتاق بودم که از مزایای وراثت، محصورسازی و چند ریختی استفاده می کردم. سه ستون شی گرایی. مشتاق بودم وعده"قابلیت استفاده مجدد"را بدست آورم و از دانش کسانی که پیش از من در این منظره جدید و جالب بوده اند استفاده کنم. نمی توانم هیجانم را هنگام فکرکردن به شبیه سازی دنیای واقعی در کلاس ها توصیف کنم و انتظار داشتم همه چیز درست پیش برود. در نگاه اول، به نظر می رسد که وراثت بزرگترین مزیت الگو شی گرا است. به نظر می رسد که تمام مثال های ساده گرایانه از سلسله مراتب shape که به عنوان مثال جلو تازه وارد ها رژه میروند، منطقی هستند. و'استفاده مجدد' کلمه روز است. نه. سال و شاید همیشه. من همه این ها را قورت دادم و با بینش تازه خود به جهان حمله کردم! با ایمان در قلبم و مشکلاتی برای حل، شروع کردم به ساخت سلسله مراتب کلاس و نوشتن کد. و همه با جهان هماهنگ بود. هرگز آن روز را فراموش نخواهم کرد که من با ارث بردن از یک کلاس، آماده استفاده از وعده ''استفاده مجدد '' شدم. این لحظه ای بود که هنوز منتظر ان هستم. یک پروژه جدید گرفتم و به آن کلاس که در پروژه قبلی ایجاد کرده بودم برگشتم. مشکلی نیست. دوباره استفاده کنید تا نجات پیدا کنید. تنها کاری که باید انجام دهم این است که آن کلاس را از پروژه دیگر بگیرم و از آن استفاده کنم. خوب. در واقع. نه فقط آن کلاس. ما به کلاس پدر هم احتیاج داریم. اما. اما این صبر کنید. صبر کنید. به نظر می رسد ما به پدر پدر نیز احتیاج داریم. و بعد. ما به همه والدین احتیاج داریم. باشه. باشه. من این کار رو میکنم مشکلی نیست. یک نقل قول عالی توسط جو آرمسترانگ، خالق ارلانگ وجود دارد: راه حل جنگل, میمون, موز من می‌توانم این مشکل را با ایجاد نکردن سلسله‌مراتب های عمیق حل کنم. اما اگر وراثت کلید استفاده مجدد باشد، مطمینا هر محدودیتی که در آن مکانیزم قرار خواهم داد، مزایای استفاده مجدد را محدود می کند. درسته؟ درست. نگهدار و واگذار کن ( Contain and Delegate - اگر معادل مناسب تری بلدید بهم بگید)بعدا درباره این مورد صحبت خواهم کرد. مسیله لوزی دیر یا زود، مشکل زیر زشتی خودشو نشون میده و بسته به زبان، مشکلی حل نشدنی است. اکثر زبانهای شی گرا از این امر پشتیبانی نمی کنند، حتی اگر به نظر می رسد منطقی باشد. پشتیبانی از این امر در زبانهای شی گرا چقدر دشوار است؟ خوب، شبه کد زیر را تصور کنید: توجه کنید که هم کلاس Scanner و هم کلاس Printer تابعی به نام start را پیاده سازی میکنند. بنابراین کلاس Copier کدام تابع start را به ارث می برد؟ Scanner یا Printer ؟ هر دو نمی توانند باشند. راه حل لوزی راه حل ساده است. این کار را نکنید. بله درست است. اکثر زبانهای شی گرا به شما اجازه نمی دهند این کار را انجام دهید. اما، اما. اگر مجبور شدم این را مدل را پیاده سازی کنم چه؟ پس استفاده مجدد چی شد؟ شما باید نگه دارید و واگذار کنید. توجه کنید که کلاس Copier اکنون نمونه ای از Printer و Scanner را دارد. این پیاده سازی تابع start را به کلاس Printer واگذار می کند. این می تواند به راحتی به Scanner واگذار شود. این مشکل شکاف دیگری در ستون وراثت است. مشکل کلاس شکننده بنابراین من سلسله مراتب خود را کم عمق می کنم و آنها را چرخه ای نگه می دارم. هیچ لوزی وجود ندارد. و همه با جهان سازگار هستند. تا اینکه. یک روز کد من کار می کند و روز دیگر کار نمیکند. ولی. خوب، شاید یک باگ باشد. اما صبر کنید. مشکل از کد من نیست. معلومه تغییر از کلاسی که از ان ارث بری کرده ام بوده. چطور ممکنه تغییر در کلاس پایه ( base ) کد من را خراب کند؟ ؟ اینجوریاس. کلاس پایه زیر را تصور کنید (با جاوا نوشته شده، اما اگر جاوا بلد نیستید هم فهم ان باید آسان باشد) : مهم: خط کد کامنت شده را به خاطر بسپارید. این خط بعدا تغییر خواهد کرد که باعث خراب شدن اوضاع خواهد شد. این کلاس دارای دو تابع در رابط کاربری خود است start و addAll . تابع add یک عنصر واحد اضافه می کند و addAll ( با فراخوانی تابع add ، چندین عنصر را اضافه می کند. و اینجا کلاس مشتق شده است: کلاس ArrayCount کلاسی خاص از کلاس اصلی Array است. تنها تفاوت رفتاری این است که ArrayCount تعداد عناصر را نگه می دارد (در متغیر count ) بیایید با جزییات به هر دو کلاس نگاه کنیم. تابع Array . add عنصری را به ArrayList محلی اضافه می کند. تابع Array . addAll ارایه ArrayList محلی را برای افزودن هر عنصر فراخوانی می کند. تابع ArrayCount . add , تابع add کلاس پدر خود را فراخوانی میکند و مقدار count را افزایش میدهد. تابع ArrayCount . addAll , تابع addAll کلاس پدر خود را فراخوانی میکند و با توجه به تعداد عناصر ان مقدار count را افزایش میدهد. و همه خوب کار می کنند حالا خط کد کامنت شده در کلاس پایه به شرح زیر تغییر می کند: و هنوز همه تست ها را پاس میکند. اما کلاس پدر به کلاس مشتق شده توجهی ندارد. حالا ArrayCount . addAll متد addAll کلاس پدر خود را فراخوانی میکند. که خودش متد add را که به صورت داخلی توسط کلاس مشتق شده override شده را فراخوانی میکند. این باعث میشد که مقدار count هر بار که متد add فراخوانی میشود افزایش یابد و وقتی متد اگر این میتواند اتفاق بیفتد, و اتفاق بیفتد، نویسنده کلاس Derived باید نحوه پیاده سازی کلاس پایه Base را بداند. و آنها باید از هر تغییری در کلاس پایه مطلع شوند زیرا این امر می تواند کلاس مشتق آنها را به روش های غیرقابل پیش بینی تغییر دهد. اوه! این شکاف بزرگ برای همیشه ثبات ستون ارزشمند وراثت را تهدید می کند. راه حل کلاس شکننده یک بار دیگر نگه دارید و واگذار کنید. با استفاده از نگه داشتن و واگذار کردن، از برنامه نویسی White Box به برنامه نویسی Black Box می رویم. در برنامه نویسی White Box باید به اجرای کلاس پایه توجه کنیم. در برنامه نویسی Black Box ، می توانیم پیاده سازی کلاس پایه را کاملا نادیده بگیریم زیرا نمی توانیم با override کردن یکی از متد های آن، کد را به کلاس پایه تزریق کنیم. ما فقط باید خود را نگران واسط - interface کنیم. این روند نگران کننده است. قرار بود وراثت برای"استفاده مجدد" یک برد بزرگ باشد. زبانهای شیء گرا، نگه داشتن و واگذار کردن را به راحتی انجام نمی دهند. اینها طراحی شده اند تا وراثت را ساده کنند. اگر مثل من هستید، شروع به تعجب در مورد این موضوع وراثت می کنید. اما مهمتر از این، این باید اعتماد شما را نسبت به قدرت طبقه بندی از طریق سلسله مراتب متزلزل کند. مسیله سلسله مراتب هر وقت در یک شرکت جدید شروع به کار می کنم، وقتی مکانی را برای قرار دادن اسناد شرکتم ایجاد می کنم با این مشکل دست و پنجه نرم می کنم. کتابچه راهنمای کارمندان. آیا پوشه ای به نام Document ایجاد می کنم و سپس یک پوشه به نام Company در آن ایجاد می کنم؟ یا آیا من یک پوشه به نام Company ایجاد می کنم و سپس یک پوشه به نام Document را در آن ایجاد می کنم؟ هر دو کار می کنند. اما کدام درست است؟ کدام بهتر است؟ مایده سلسله مراتب طبقه بندی شده این بود که کلاسهای پایه (والدین) وجود داشتند که عمومی تر بودند و کلاسهای مشتق شده (فرزندان) نسخه های خاص تر آن کلاسها بودند. و وقتی در زنجیره وراثت پایین میرویم خاص تر هم میشود. (سلسله مراتب shape را در بالا مشاهده کنید) اما اگر پدر و فرزند می توانند خودسرانه خود را تغییر دهند، پس به وضوح چیزی در مورد این مدل اشتباه است. راه حل سلسله مراتب چیزی که که درست نیست اینه که. پس سلسله مراتب به چه دردی میخورد؟ دارا بودن - محتوی) - containment اگر به دنیای واقعی نگاه کنید، سلسله مراتب محتوی (یا مالکیت انحصاری) را در همه جا مشاهده خواهید کرد. آنچه شما پیدا نخواهید کرد سلسله مراتب طبقه بندی شده است. بگذار کمی به آن فکر کنم. الگو شی گرایی مبتنی بر جهان واقعی بود. اما از یک مدل ناقص استفاده می کند. سلسله مراتب طبقه بندی شده، هیچ شباهتی به دنیای واقعی ندارند. اما دنیای واقعی مملو از سلسله مراتب های محتوی است. یک نمونه عالی از یک سلسله مراتب محتوی جورابهای شما است. آنها در یک کشو جوراب قرار دارند که کشو در کمد شما است و کمد در اتاق خواب شما قرار دارد که اتاق خواب هم در خانه شما قرار دارد و غیره. فولدر های هارد دیسک شما نمونه دیگری از سلسله مراتب های محتوی هستند. آنها حاوی فایل هستند. پس چگونه طبقه بندی می کنیم؟ خوب، اگر به اسناد شرکت فکر می کنید، اهمیتی ندارد که آنها را کجا قرار میدهم. می توانم آنها را در پوشه Documents یا پوشه ای بنام Stuff قرار دهم. من انها را با برچسب (تگ خودمون) دسته بندی میکنم. تگ ها ترتیب یا سلسله مراتب خاصی ندارند. (این مشکل لوزی را هم حل میکند) تک ها با واسط - interface ها مشابه هستند زیرا می توانید انواع مختلفی از Document داشته باشید. اما با وجود ترک های زیاد، به نظر می رسد ستون وراثت سقوط کرده است. خداحافظ، وراثت محصور سازی - Encapsulation , سقوط دومین ستون در نگاه اول، به نظر می رسد محصور سازی دومین مزیت بزرگ برنامه نویسی شی گرا است. متغیرهایی که وضعیت شی را توصیف میکنند (فیلد ها) از دسترسی خارجی محافظت می شوند، یعنی آنها در شی محصور شده اند. دیگر لازم نیست که نگران متغیرهای جهانی ( global ) باشیم که خدا میداند چه کسانی به ان دسترسی دارند. محصور سازی امنیت متغیرهای شما را تضمین میکند. این چیز محصور سازی باور نکردنیه! زنده باد محصور سازی. تا اینکه. مشکل ارجاع - Reference برای کارآیی، اشیاء به واسطه ارجاع - Reference شان در دسترس توابع قرار میگیرد نه مقدارشان. این بدان معنی است که توابع یک شی نمیگیرند، بلکه یک ارجاع یا اشاره گر را به شی را میگیرند. اگر ارجاع یک شی به یک سازنده شی - Object Constructor پاس شود, سازنده می تواند ارجاع شی را در یک متغیر خصوصی - private قرار دهد که توسط محصور سازی محافظت می شود. چرا نه؟ چون قطعه کد های دیگر یک ارجاع به شی را دارند. مثلا کد های درون سازنده. ایا حتما باید یک ارجاع از شی داشته باشیم وگرنه نمیتوانیم انرا به سازنده پاس کنیم؟ راه حل ارجاع سازنده باید انها در شی کلون کند. و نه یک کلون کم عمق بلکه یک کلون عمیق. یعنی هر شی پاس شده و شی های درون انها و غیره. برای بهره وری بیش از حد زیاده. و این یک مشکل است. همه اشیاء نمی توانند کلون شوند. برخی از منابع سیستم عامل در ارتباط با آنها، کلون کردن را در بهترین حالت یا در بدترین حالت غیر ممکن است. و هر زبان شی گرا اصلی این مشکل را دارد. خداحافظ، محصور سازی. چند ریختی, سقوط سومین ستون چند ریختی بد نیست اما برای دستیابی به ان حتما نیاز به یک زبان شی گرا نداریم. واسط - interface ها این را به شما می دهند. بدون خرت و پرت های شی گرایی. و با واسط ها، محدودیتی برای تعداد رفتارهای مختلف که می توانید با هم مخلوط کنید وجود ندارد. بنابراین بدون هیچ گونه آزار و اذیت، ما با چند ریختی شی گرا خداحافظی می کنیم و به چند ریختی مبتنی بر واسط سلام میکنیم. قول های شکسته شده خوب، شی گرایی مطمینا در روزهای اولیه وعده های زیادی داده است. و این وعده ها هنوز به برنامه نویسان ساده لوح که در کلاسهای درس نشسته اند، وبلاگ ها را می خوانند و دوره های آنلاین را می گذرانند، داده می شود. برای درک اینکه چطور شی گرایی به من دروغ گفت، سالها طول کشید. من هم بسیار چشم بسته و بی تجربه اعتماد داشتم. خداحافظ، برنامه نویسی شی گرا. حالا چیکار کنیم؟ سلام، برنامه نویسی functional . کار با شما در طول چند سال گذشته بسیار خوب بوده است. فقط می دانید، من هیچ یک از وعده های شما را ارزش نمیدانم. من باید آن را ببینم تا آن را باور کنم. پادکست که توسط اقای سمیر رحمانی تهیه شده در مورد تفاوت زبان های شی گرا و فانکشنال صحبت میکنه. شنیدن اش رو به همه توصیه میکنم. خود اقای رحمانی هم اشاراتی به این مقاله دارند. مطالعه های اینده