پایان برنامه‌نویسی شی‌گرا نزدیک است

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

پایان برنامه‌نویسی شی‌گرا نزدیک است

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

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

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

وراثت یا ارث‌بری در نگاه اول بزرگ‌ترین مزیت برنامه‌نویسی شی‌گرا به شمار می‌رود. در برنامه‌نویسی شی‌گرا هر شی یک نمونه از کلاس است که می‌تواند ویژگی‌های خود را از کلاس‌های دیگر به ارث ببرد و در عین حال ویژگی‌های خاص خودش را داشته باشد. به‌عنوان مثال در شکل بالا «نقطه»، زیرکلاس «دایره‌ی توپُر» است. «دایره‌ی توپر» زیرکلاس «دایره» است و «دایره» زیرکلاس «شکل» است درواقع «شکل» کلاس والد است و سایر کلاس‌ها فرزند هستند. وراثت باعث صرفه‌جویی در نوشتن کد می‌شود اما معایبی نیز دارد.
۱- مسئله‌ی موز میمون جنگل

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

درواقع به والدِ آن کلاسِ والد و تمام‌ والدهای بعدی نیز احتیاج خواهید داشت. اگر فکر می‌کنید مسئله به همینجا ختم می‌شود در اشتباه هستید؛ زیرا اگر شی شما شامل شی دیگری باشد، به آن شی نیز نیاز خواهید داشت. همچنین به والد آن شی، به والدِ والد آن شی و تمام والدهای آن شی نیز نیاز خواهد بود.

جو آرمسترانگ، خالق زبان برنامه‌نویسی ارلنگ (Erlang) می‌گوید:

مشکل زبان‌های برنامه‌نویسی شی‌گرا این است که همه‌ی آن‌ها یک محیط ضمنی دارند که با خود حمل می‌کنند. شما یک موز می‌خواهید اما به گوریلی برمی‌خورید که آن موز و کل جنگل را در اختیار دارد.

راه‌حل مسئله‌ی موز میمون جنگل

برای حل این مشکل می‌توان از ساختن سلسله مراتب عمیق خودداری کرد. اما اگر هدف از ارث‌بری، استفاده‌ی مجدد باشد، آنگاه محدودکردن این مکانیزم، مزایای استفاده‌ی مجدد را نیز محدود خواهد کرد.
۲- مسئله‌ی الماس

هر دو کلاس اسکنر (Scanner) و پرینتر (Printer) تابعی به نام استارت (start) را پیاده‌سازی می‌کنند. اگر در تصویر دقت کنید کلاس کُپیر (Copier) از هر دو کلاس اسکنر و پرینتر ارث می‌برد بنابراین این سؤال پیش می‌آید که کلاس کُپیر (Copier) کدام تابع استارت را به ارث می‌برد: تابعی که در کلاس اسکنر نوشته شده یا تابعی که در کلاس پرینتر نوشته شده؟ یا هردو؟
راه‌حل مسئله‌ی الماس

راه‌حل ساده این است که این کار را نکنیم. بسیاری از زبان‌های برنامه‌نویسی شی‌گرا اجازه‌ی چنین کاری را نمی‌دهند. اما برای مدل کردن و استفاده‌ی مجدد باید چه کار کرد؟ پاسخ استفاده از Contain و Delegate است. قطعه کد زیر را در نظر بگیرد.

در این قطعه کد، کلاس کپیر به‌عنوان نمونه‌ای از کلاس اسکنر و پرینتر تعریف شده و از تابع استارت کلاس پرینتر استفاده می‌کند. در خط آخر اگر به‌جای ()printer.start بنویسم ()scanner.start در این صورت از تابع کلاس اسکنر استفاده خواهد کرد.

اما این راه‌حل نیز باعث بروز مشکل دیگری در اصل ارث‌بری خواهد شد.

۳- مسئله‌ی کلاس پایه‌ی شکننده

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

دلیل این است که تغییراتی در کلاس والد اعمال شده و کل کدها را با مشکل مواجه کرده است. تغییر در کلاس پایه چگونه می‌تواند کل برنامه را با مشکل مواجه کند؟ قطعه کد زیر که به زبان جاوا نوشته شده را در نظر بگیرید:

به قسمتی از کد که با // جدا شده دقت کنید، این قسمت بعدا تغییر خواهد کرد. این کد دو تابع به نام‌های ()add و ()addall دارد. تابع ()add یک تک عنصر را با مقدار قبلی جمع می‌کند و تابع ()addall تعدادی از عناصر را با فراخوانی تابع ()add با مقدار قبلی جمع می‌کند. حال کلاس مشتق‌شده‌ی زیر را در نظر بگیرید:

کلاس ArrayCount از کلاس Array مشتق شده با این تفاوت که تعداد عناصر را در متغیر Count ذخیره می‌کند. حال جزییات این دو کلاس را بررسی می‌کنیم.

()Array add (تابع add استفاده‌شده در کلاس Array) عنصری را به ArrayList اضافه می‌کند.

Array addAll() ،ArrayList را برای هر عنصر فراخوانی می‌کند.

()ArrayCount add (تابع add استفاده‌شده در کلاس ArrayCount) ابتدا تابع add والد خود را صدا می‌زند و سپس متغیر count را یک واحد افزایش می‌دهد.

()ArrayCount addAll ابتدا تابع ()addAll والد خود را صدا می‌زند و سپس متغیر count را به تعداد اعداد افزایش می‌دهد.

 

منبع : زومیت