این پست ترجمه Goodbye, Object Oriented Programming در مدیومه که همین الان 158000 لایک خورده. پس فکر میکنم ارزش چند بار خوندن رو داره. به نظر من ترجمه این جور مقاله ها یکم سخته چون همه اصطلاحات برنامه نویسی انگلیسی هستند و اصل مقاله هم انگلیسیه و یه جورایی با هم دیگه هماهنگ هستند. به خاطر همین مجبور شدم یکم مقاله رو تغییر و خلاصه کنم که منظور رو بهتر برسونم- امیدوارم. ولی اصل قضیه هیچ تغییری نکرده.
لذت ببرید...
من چند دهه است که با زبان های شی گرا برنامه نویسی می کنم. اولین زبان شی گرایی که استفاده کردم C ++ و سپس Smalltalk و در آخر .NET و Java بود.
خیلی مشتاق بودم که از مزایای وراثت ، محصورسازی و چند ریختی استفاده می کردم. سه ستون شی گرایی.
مشتاق بودم وعده "قابلیت استفاده مجدد "را بدست آورم و از دانش کسانی که پیش از من در این منظره جدید و جالب بوده اند استفاده کنم.
نمی توانم هیجانم را هنگام فکرکردن به شبیه سازی دنیای واقعی در کلاس ها توصیف کنم و انتظار داشتم همه چیز درست پیش برود.
بیشتر از این نمیتوانستم اشتباه کنم!
در نگاه اول ، به نظر می رسد که وراثت بزرگترین مزیت الگو شی گرا است. به نظر می رسد که تمام مثال های ساده گرایانه از سلسله مراتب shape که به عنوان مثال جلو تازه وارد ها رژه میروند ، منطقی هستند.
و‘استفاده مجدد’ کلمه روز است. نه ... سال و شاید همیشه .
من همه این ها را قورت دادم و با بینش تازه خود به جهان حمله کردم!
با ایمان در قلبم و مشکلاتی برای حل ، شروع کردم به ساخت سلسله مراتب کلاس و نوشتن کد. و همه با جهان هماهنگ بود.
هرگز آن روز را فراموش نخواهم کرد که من با ارث بردن از یک کلاس ، آماده استفاده از وعده ‘’استفاده مجدد ‘’ شدم. این لحظه ای بود که هنوز منتظر ان هستم .
یک پروژه جدید گرفتم و به آن کلاس که در پروژه قبلی ایجاد کرده بودم برگشتم .
مشکلی نیست . دوباره استفاده کنید تا نجات پیدا کنید. تنها کاری که باید انجام دهم این است که آن کلاس را از پروژه دیگر بگیرم و از آن استفاده کنم.
خوب ... در واقع ... نه فقط آن کلاس. ما به کلاس پدر هم احتیاج داریم. اما ... اما این
صبر کنید ... صبر کنید ... به نظر می رسد ما به پدر پدر نیز احتیاج داریم ... و بعد ... ما به همه والدین احتیاج داریم. باشه ... باشه ... من این کار رو میکنم مشکلی نیست.
یک نقل قول عالی توسط جو آرمسترانگ ، خالق ارلانگ وجود دارد:
مشکل زبانهای شی گرا این است که آنها این همه محیط ضمنی را با خود حمل میکنند. شما یک موز می خواستید اما آنچه شما گرفتید یک میمون, موز و کل جنگل بود
من میتوانم این مشکل را با ایجاد نکردن سلسلهمراتب های عمیق حل کنم.اما اگر وراثت کلید استفاده مجدد باشد ، مطمئناً هر محدودیتی که در آن مکانیزم قرار خواهم داد ، مزایای استفاده مجدد را محدود می کند. درسته؟
درست.
نگهدار و واگذار کن (Contain and Delegate- اگر معادل مناسب تری بلدید بهم بگید).بعداً درباره این مورد صحبت خواهم کرد.
دیر یا زود ، مشکل زیر زشتی خودشو نشون میده و بسته به زبان ، مشکلی حل نشدنی است.
اکثر زبانهای شی گرا از این امر پشتیبانی نمی کنند ، حتی اگر به نظر می رسد منطقی باشد. پشتیبانی از این امر در زبانهای شی گرا چقدر دشوار است؟
خوب ، شبه کد زیر را تصور کنید:
Class PoweredDevice { } Class Scanner inherits from PoweredDevice { function start() { } } Class Printer inherits from PoweredDevice { function start() { } } Class Copier inherits from Scanner, Printer { }
توجه کنید که هم کلاس Scanner و هم کلاس Printer تابعی به نام start را پیاده سازی میکنند.
بنابراین کلاس Copier کدام تابع start را به ارث می برد؟ Scanner یا Printer؟ هر دو نمی توانند باشند.
راه حل ساده است. این کار را نکنید.
بله درست است. اکثر زبانهای شی گرا به شما اجازه نمی دهند این کار را انجام دهید.
اما ، اما ... اگر مجبور شدم این را مدل را پیاده سازی کنم چه؟ پس استفاده مجدد چی شد؟
شما باید نگه دارید و واگذار کنید.
Class PoweredDevice { } Class Scanner inherits from PoweredDevice { function start() { } } Class Printer inherits from PoweredDevice { function start() { } } Class Copier { Scanner scanner Printer printer function start() { printer.start() } }
توجه کنید که کلاس Copier اکنون نمونه ای از Printer و Scanner را دارد. این پیاده سازی تابع start را به کلاس Printer واگذار می کند. این می تواند به راحتی به Scanner واگذار شود.
این مشکل شکاف دیگری در ستون وراثت است.
بنابراین من سلسله مراتب خود را کم عمق می کنم و آنها را چرخه ای نگه می دارم. هیچ لوزی وجود ندارد .
و همه با جهان سازگار هستند. تا اینکه...
یک روز کد من کار می کند و روز دیگر کار نمیکند . ولی... من که کد را تغییر ندادم.
خوب ، شاید یک باگ باشد ... اما صبر کنید ... چیزی تغییر نکرده ...
مشکل از کد من نیست. معلومه تغییر از کلاسی که از ان ارث بری کرده ام بوده.
چطور ممکنه تغییر در کلاس پایه(base) کد من را خراب کند ؟؟
اینجوریاس ...
کلاس پایه زیر را تصور کنید (با جاوا نوشته شده ، اما اگر جاوا بلد نیستید هم فهم ان باید آسان باشد):
import java.util.ArrayList; public class Array { private ArrayList<Object> a = new ArrayList<Object>(); public void add(Object element) { a.add(element); } public void addAll(Object elements[]) { for (int i = 0; i < elements.length; ++i) a.add(elements[i]); // this line is going to be changed } }
**مهم**: خط کد کامنت شده را به خاطر بسپارید. این خط بعداً تغییر خواهد کرد که باعث خراب شدن اوضاع خواهد شد.
این کلاس دارای دو تابع در رابط کاربری خود است start و addAll. تابع add یک عنصر واحد اضافه می کند و addAll () با فراخوانی تابع add ، چندین عنصر را اضافه می کند.
و اینجا کلاس مشتق شده است:
public class ArrayCount extends Array { private int count = 0; @Override public void add(Object element) { super.add(element); ++count; } @Override public void addAll(Object elements[]) { super.addAll(elements); count += elements.length; } }
کلاس ArrayCount کلاسی خاص از کلاس اصلی Array است. تنها تفاوت رفتاری این است که ArrayCount تعداد عناصر را نگه می دارد(در متغیر count).
بیایید با جزئیات به هر دو کلاس نگاه کنیم.
تابع Array.add عنصری را به ArrayList محلی اضافه می کند.
تابع Array.addAll ارایه ArrayList محلی را برای افزودن هر عنصر فراخوانی می کند.
تابع ArrayCount.add , تابع add کلاس پدر خود را فراخوانی میکند و مقدار count را افزایش میدهد.
تابع ArrayCount.addAll , تابع addAll کلاس پدر خود را فراخوانی میکند و با توجه به تعداد عناصر ان مقدار count را افزایش میدهد.
و همه خوب کار می کنند
حالا خط کد کامنت شده در کلاس پایه به شرح زیر تغییر می کند:
public void addAll(Object elements[]) { for (int i = 0; i < elements.length; ++i) add(elements[i]); // this line was changed }
و هنوز همه تست ها را پاس میکند.
اما کلاس پدر به کلاس مشتق شده توجهی ندارد.
حالا ArrayCount.addAll متد addAll کلاس پدر خود را فراخوانی میکند. که خودش متد add را که به صورت داخلی توسط کلاس مشتق شده override شده را فراخوانی میکند.
این باعث میشد که مقدار count هر بار که متد add فراخوانی میشود افزایش یابد و وقتی متد addAll کلاس مشتق شده فراخوانی میشود دوباره افزایش یابد.
دو بار حساب شده
اگر این میتواند اتفاق بیفتد ,و اتفاق بیفتد ، نویسنده کلاس Derived باید نحوه پیاده سازی کلاس پایهBase را بداند. و آنها باید از هر تغییری در کلاس پایه مطلع شوند زیرا این امر می تواند کلاس مشتق آنها را به روش های غیرقابل پیش بینی تغییر دهد.
اوه! این شکاف بزرگ برای همیشه ثبات ستون ارزشمند وراثت را تهدید می کند.
یک بار دیگر نگه دارید و واگذار کنید. با استفاده از نگه داشتن و واگذار کردن، از برنامه نویسی White Box به برنامه نویسی Black Box می رویم. در برنامه نویسی White Box باید به اجرای کلاس پایه توجه کنیم.
در برنامه نویسی Black Box ، می توانیم پیاده سازی کلاس پایه را کاملاً نادیده بگیریم زیرا نمی توانیم با override کردن یکی از متد های آن ، کد را به کلاس پایه تزریق کنیم. ما فقط باید خود را نگران واسط-interface کنیم.
این روند نگران کننده است ... قرار بود وراثت برای "استفاده مجدد" یک برد بزرگ باشد. زبانهای شیء گرا ، نگه داشتن و واگذار کردن را به راحتی انجام نمی دهند. اینها طراحی شده اند تا وراثت را ساده کنند.
اگر مثل من هستید ، شروع به تعجب در مورد این موضوع وراثت می کنید. اما مهمتر از این ، این باید اعتماد شما را نسبت به قدرت طبقه بندی از طریق سلسله مراتب متزلزل کند.
هر وقت در یک شرکت جدید شروع به کار می کنم ، وقتی مکانی را برای قرار دادن اسناد شرکتم ایجاد می کنم با این مشکل دست و پنجه نرم می کنم. کتابچه راهنمای کارمندان.
آیا پوشه ای به نام Document ایجاد می کنم و سپس یک پوشه به نام Company در آن ایجاد می کنم؟
یا آیا من یک پوشه به نام Company ایجاد می کنم و سپس یک پوشه به نام Document را در آن ایجاد می کنم؟ هر دو کار می کنند. اما کدام درست است؟ کدام بهتر است؟
مایده سلسله مراتب طبقه بندی شده
این بود که کلاسهای پایه (والدین) وجود داشتند که عمومی تر بودند و کلاسهای مشتق شده (فرزندان) نسخه های خاص تر آن کلاسها بودند. و وقتی در زنجیره وراثت پایین میرویم خاص تر هم میشود. (سلسله مراتب shape را در بالا مشاهده کنید)
اما اگر پدر و فرزند می توانند خودسرانه خود را تغییر دهند ، پس به وضوح چیزی در مورد این مدل اشتباه است.
چیزی که که درست نیست اینه که ...
سلسله مراتب طبقه بندی شده کار نمیکند
پس سلسله مراتب به چه دردی میخورد؟
شامل بودن( دارا بودن - محتوی) - containment
اگر به دنیای واقعی نگاه کنید ، سلسله مراتب محتوی (یا مالکیت انحصاری) را در همه جا مشاهده خواهید کرد. آنچه شما پیدا نخواهید کرد سلسله مراتب طبقه بندی شده است.
بگذار کمی به آن فکر کنم. الگو شی گرایی مبتنی بر جهان واقعی بود. اما از یک مدل ناقص استفاده می کند . سلسله مراتب طبقه بندی شده، هیچ شباهتی به دنیای واقعی ندارند.
اما دنیای واقعی مملو از سلسله مراتب های محتوی است. یک نمونه عالی از یک سلسله مراتب محتوی جورابهای شما است. آنها در یک کشو جوراب قرار دارند که کشو در کمد شما است و کمد در اتاق خواب شما قرار دارد که اتاق خواب هم در خانه شما قرار دارد و غیره.
فولدر های هارد دیسک شما نمونه دیگری از سلسله مراتب های محتوی هستند. آنها حاوی فایل هستند.
پس چگونه طبقه بندی می کنیم؟
خوب ، اگر به اسناد شرکت فکر می کنید ، اهمیتی ندارد که آنها را کجا قرار میدهم. می توانم آنها را در پوشه Documents یا پوشه ای بنام Stuff قرار دهم.
من انها را با برچسب(تگ خودمون) دسته بندی میکنم.
Document Company Handbook
تگ ها ترتیب یا سلسله مراتب خاصی ندارند.(این مشکل لوزی را هم حل میکند)
تک ها با واسط-interface ها مشابه هستند زیرا می توانید انواع مختلفی از Document داشته باشید.
اما با وجود ترک های زیاد ، به نظر می رسد ستون وراثت سقوط کرده است.
خداحافظ ، وراثت
در نگاه اول ، به نظر می رسد محصور سازی دومین مزیت بزرگ برنامه نویسی شی گرا است. متغیرهایی که وضعیت شی را توصیف میکنند(فیلد ها)از دسترسی خارجی محافظت می شوند ، یعنی آنها در شی محصور شده اند. دیگر لازم نیست که نگران متغیرهای جهانی(global) باشیم که خدا میداند چه کسانی به ان دسترسی دارند.
محصور سازی امنیت متغیرهای شما را تضمین میکند.
این چیز محصور سازی باور نکردنیه !! زنده باد محصور سازی ...
تا اینکه ...
برای کارآیی ، اشیاء به واسطه ارجاع- Reference شان در دسترس توابع قرار میگیرد نه مقدارشان.
این بدان معنی است که توابع یک شی نمیگیرند ، بلکه یک ارجاع یا اشاره گر را به شی را میگیرند.
اگر ارجاع یک شی به یک سازنده شی- Object Constructor پاس شود , سازنده می تواند ارجاع شی را در یک متغیر خصوصی-private قرار دهد که توسط محصور سازی محافظت می شود.
اما شی پاس شده در امان نیست
چرا نه؟چون قطعه کد های دیگر یک ارجاع به شی را دارند.مثلا کد های درون سازنده. ایا حتما باید یک ارجاع از شی داشته باشیم وگرنه نمیتوانیم انرا به سازنده پاس کنیم؟
سازنده باید انها در شی کلون کند. و نه یک کلون کم عمق بلکه یک کلون عمیق . یعنی هر شی پاس شده و شی های درون انها و غیره .
برای بهره وری بیش از حد زیاده.
و این یک مشکل است. همه اشیاء نمی توانند کلون شوند. برخی از منابع سیستم عامل در ارتباط با آنها ، کلون کردن را در بهترین حالت یا در بدترین حالت غیر ممکن است.
و هر زبان شی گرا اصلی این مشکل را دارد.
خداحافظ ، محصور سازی.
چند ریختی بد نیست اما برای دستیابی به ان حتما نیاز به یک زبان شی گرا نداریم.
واسط-interface ها این را به شما می دهند. بدون خرت و پرت های شی گرایی.
و با واسط ها ، محدودیتی برای تعداد رفتارهای مختلف که می توانید با هم مخلوط کنید وجود ندارد.
بنابراین بدون هیچ گونه آزار و اذیت ، ما با چند ریختی شی گرا خداحافظی می کنیم و به چند ریختی مبتنی بر واسط سلام میکنیم.
خوب ، شی گرایی مطمئناً در روزهای اولیه وعده های زیادی داده است. و این وعده ها هنوز به برنامه نویسان ساده لوح که در کلاسهای درس نشسته اند ، وبلاگ ها را می خوانند و دوره های آنلاین را می گذرانند ، داده می شود.
برای درک اینکه چطور شی گرایی به من دروغ گفت ، سالها طول کشید. من هم بسیار چشم بسته و بی تجربه اعتماد داشتم.
خداحافظ ، برنامه نویسی شی گرا.
حالا چیکار کنیم؟
سلام ، برنامه نویسی functional. کار با شما در طول چند سال گذشته بسیار خوب بوده است.
فقط می دانید ، من هیچ یک از وعده های شما را ارزش نمیدانم. من باید آن را ببینم تا آن را باور کنم.
اگر این مقاله را دوست داشتید ، لایک کنید تا افراد دیگر ان را در ویرگول مشاهده کنند.
شماره ۲۲پادکست رادیو بوت که توسط اقای سمیر رحمانی تهیه شده در مورد تفاوت زبان های شی گرا و فانکشنال صحبت میکنه. شنیدن اش رو به همه توصیه میکنم . خود اقای رحمانی هم اشاراتی به این مقاله دارند.
منبع: Medium