بسیاری از زبانهای برنامهنویسی امروزی از این قرارند: C++,C ، Javad , C# , COBOL , Microsoft Visual Basic و غیره.
با وجود این همه زبان، یک مهندس نرمافزار چگونه تصمیم میگیرد که کدامیک از آنها را برای یک پروژه استفاده کند.
گاهی اوقات، یک زبان به این دلیل انتخاب میشود که تولید کنندگان یک شرکت کار با آن را دوست دارند و یا میشناسند، که این میتواند یک دلیل منطقی باشد.
گاهی اوقات یک زبان به دلیل جدید بودن و فوق العاده بودنش انتخاب میشود، که این یک ابزار بازاریابی برای جلب نظر عمومی به یک محصول میباشد، و ممکن است این دلیل منطقی به نظر نرسد.
در حالت ایدهآل، یک زبان برنامهنویسی باید بر مبنای تواناییهای آن جهت اجرای یک کار خاص انتخاب شود و حل یک مشکل باید تعیین کننده زبان باشد.
ما تنها به مقایسه زبانهای C# و جاوا میپردازیم.
برخی زبانها، همچون C++ و پاسکال، نیز در این مقایسه استفاده میشوند، اما تنها برای کمک به اثبات انگیزههای بالقوه برای ایجاد زبانهای برنامهنویسی جدیدتر با ویژگیهای جدیدتر.
اگر در زبان قدیمیتر ضعفهایی وجود دارد و در زبان جدیدتر این ضعفها دیده نمیشوند و یا از نظرها پنهان شدهاند، این مسئله میتواند بیانگر انگیزه معماران در انجام برخی تغییرات در زبان جدیدتر باشد.
شناخت این انگیزه اغلب حائز اهمیت است، چرا که در غیر اینصورت انتقاد هدفدار از یک زبان غیرممکن میشود.
مثلا، اگر ویژگی معروفی که در زبان قبلی وجود داشته از زبان جدیدتر حذف شود، یک تولید کننده برنامه کاربردی ممکن است احساس کند که زبان جدیدتر جایگزین با ارزشی برای زبان قبلی نیست، چرا که قدرت زبان قبلی را ندارد.
هر چند که زبان جدیدتر ممکن است واقعا ویژگیهای موثری را در اختیار او قرار دهد و او را از گرفتار شدن در برخی مشکلات شناخته شده حفظ نماید.
تولید جاوا به قبل C# باز میگردد، و C# جدای از دیگر زبانها ایجاد نشد.
کاملا طبیعی است که C# در برگیرنده نقاط قوت و ضعف جاوا است، درست مانند جاوا که برگرفته از Objective – C بود و آن هم برگرفته از C و به همین ترتیب.
بنابراین، C# نباید متفاوت از جاوا باشد.
اگر جاوا کامل بود، دیگر دلیلی برای ایجاد C# وجود نداشت.
اگر C# کامل باشد، دیگری دلیلی برای ایجاد زبان برنامهنویسی جدیدتر وجود ندارد.
بهرحال، آینده نامشخص است، و هم اکنون C# و جاوا زبانهای برنامهنویسی شیءگرای خوبی هستند.
شباهتهای بین C# و جاوا
از نقطه نظر تولید کننده برنامه کاربردی، C# و جاوا کاملا شبیه هم هستند، در این بحث به شباهتهای اصلی این دو زبان خواهیم پرداخت.
تمامی آبجکتها مرجع هستند
انواع مرجعها بسیار شبیه اشارهگرها (pointer) در C++ هستند، به خصوص وقتی که شناسهای را برای برخی نمونههای جدید کلاس تنظیم میکنید.
اما هنگام دستیابی به نمونههای دادهها در C++ است که در پشته ایجاد میشوند.
تمامی نمونههای کلاس با استفاده از اپراتور جدید در هیپ ایجاد میشوند، اما استفاده از delete مجاز نیست چرا که هر دو زبان از روشهای garbage collection متعلق به خود استفاده میکنند.
Garbage Collection
طبیعتا، یاری نکردن حافظه مشکل بزرگی در زبانهای نظیر C++ است.
این فوقالعاده است که شما بتوانید بطور پویا نمونههای کلاس را در زمان اجرا در هیپ ایجاد کنید، اما مدیریت حافظه میتواند مشکلساز باشد.
C# و جاوا هر دو دارای garbage collection توکار هستند.
به عبارتی برای آزادسازی حافظه دیگر نیازی به فراخوانی delete نیست.
هیچ زبانی اجازه تسهیم کردن Object ای را که قابل مصرف است به شما نمیدهد.
اما ممکن است از شما خواسته شود تا new را حتی بیشتر از آنچه که دوست دارید، فرا بخوانید.
علت این مسئله آن است که در هر دو زبان تمامی Object ها در هیپ ایجاد میشوند، به این معنی که چنین چیزی در هر زبانی قابل قبول نیست.
Class BadaBing
{
Public BadaBing ( )
{
}
}
BadaBing badaBing ( ) ; // You can’t create
temporary data but You must use parens on
a constructor
کامپایلر پیغام کوتاهی را در این باره برای شما میفرستد، چرا که شما سعی میکنید ذخیرهسازی موقتی را ایجاد کنید.
باید این کار را انجام دهید:
BadaBing badaBing = new BadaBing ( ) ;
حال badaBoom ساخته شد و دارای حداقل یک مرجع است.
بعد، ممکن است بخواهید تا از دست آن خلاص شوید.
delete BadaBoom; // illegal in C# and
Java – the compiler will complain
تا جائیکه میخواهید از badaBoom استفاده کنید، سپس زمانیکه تصمیم میگیرید مرجع خود را به دیگری بدهید، garbage colletor شما را از دست آن خلاص میکند.
بسیاری از تولید کنندگان برنامههای کاربردی از garbage collection شکایت دارند، شاید آنها کنترل میخواهند.
شاید با وجود این امکان احساس میکنند برنامهنویسان واقعی نیستند، چرا که نمیتوانند Object ای را که ایجاد کردهاند، delete کنند.
شاید داشتن یک زبان بسیار پیچیده و مستعد خطا، مالکیت کد را از جانب تولید کننده اصلی به مدت طولانی تضمین میکند.
بدون توجه به این دلایل garbage collection دارای مزایایی است که برخی از آنها از این قرارند:
1 عدم یاری حافظه.
این مزیت مشخصی است.
هر دو روش garbage collection تضمین میکنند تا در برخی مواقع هنگام اجرای برنامه، تمامی آبجکت را حذف کنند، اما هیچکدام زمان آن را تضمین نمیکنند، جز اینکه هیچ آبجکتی حذف نمیگردد تا زمانی که حداقل تمام ارجاعات برنامه به آن حذف گردد.
2ـ garbage collection تولید کنندگان را تشویق به نوشتن کدهای شیءگرای بیشتر میکند.
این یک مزیت ظریف و دقیق است.
مثلا، در C++، تولیدکنندگانی که متدهای کلاس را ایجاد میکنند باید دادههایی را بازگردانند که معمولا مرجع non-const یا پارامترهای اشارهگر را در هنگام اجرای متد تنظیم میکنند، یا باید نمونه کلاسی از نوع دیگر را باز گردانند که ترکیبی از تمام دادههای ضروری را نگاه میدارد.
به نظر میرسد مورد دوم بهتر از مورد اول باشد.
چه کسی میخواهد تابعی با10 پارامتر را فرا بخواند؟
و پارامترهای بیشتری که بین سرویس گیرنده و کد سرویس دهنده رد و بدل میشوند، درگیری بیشتری ایجاد میکند، که این اصلا خوب نیست.
مثلا، اگر بعدا مشخص شود که تابعی نیاز به بازگرداندن دادههای بیشتری دارد، تنها لازم است این اجرای تابع با یک افزایش به کلاس مرکب، که ممکن است برای سرویس گیرنده تنها یک recompiler باشد، تغییر داده شود.
نه تنها از این جهت، بلکه زمانیکه یک تابع تنها یک آبجکت را باز میگرداند، این تابع میتواند با دیگر فراخوانیهای تابع تو در تو شود، در حالیکه دادههای بازگشتی با پارامترهای in/out مانع این تو در تویی میشوند.
هنگامیکه آبجکتها با این متد بهتر بازگردانده میشوند، معمولا تولید کننده تنها دو گزینش پیش رو دارد: بازگرداندن یک کپی از دادههای موقت که در تابع ساخته و مقداردهی اولیه میشوند، یا ایجاد اشارهگر جدید آبجکت در تابع، side – effect کردن مقادیر derefrence شده آن، سپس بازگرداندن اشارهگر، که مطمئن است، چرا که کامپایلر، اشارهگرها یا دادههای هیپ را در میان فراخوانیهای توابع خراب نخواهد کرد.
با وجود اینکه بازگرداندن یک اشارهگر دارای مزایایی است (یک سازنده کپی نباید فراخوانی شود بنابراین ممکن است سریعتر باشد، خصوصا با دادههای بزرگتر، زیر کلاسی از یک نوع اشارهگر میتواند برای گسترده شدن به فراخوانده بازگردانده شود)، اما در C++ از اشکالات جدی نیز برخوردار است: حال سرویس گیرنده باید با فراخوانی delete در اشارهگر بازگشتی، به مدیریت حافظه توجه کند.
برای این کار راههایی وجود دارد، یکی از این راهها شمارش مرجع با استفاده از الگوهای عمومی است (برای اطلاعات بیشتر به سایت زیر مراجعه کنید.
Ms-help : //MS.
VSCC/MS.MSDNVS/vbcon/html/ Vbcon Reference Counting Garbage Collection Object Lifetime.
htm بهرحال، شمارش مرجع به دلیل نحوه گرامری الگو کاملا رضایتبخش نیست، و اگر زمانیکه طرحهای garbage collection جاوا و C# کار میکنند، اجراهای شمارش چرخهها را به درستی اداره نکند، وضعیت بدتر هم میشود (یک چرخه از شمارش مرجع سادهای استفاده میکند: اگر دو آبجکت به یکدیگر ارجاع داشته باشند، و سپس تنها مرجعات بیرونی برای هر دو منتشر شود، هیچ کدام delete نمیشوند، زیرا هر کدام یک مرجع دیگر دارند، و هر Object تا زمانیکه شماره مرجع آن به صفر برسد delete نمیشود.
بنابراین، تولید کنندگان معمولا ایمنترین راه را انتخاب میکنند، و تنها یک کپی از نوع کلاس شناخته شده زمان کامپایل را باز میگردانند.
اما از آنجائیکه هر دو زبان C# و جاوا از garbage collection استفاده میکنند، تولید کنندگان تشویق میشوند هنگام نوشتن الگوسازیهای تابع (بجای استفاده از پارامترهای داخلی / خارجی) ارجاع جدید به دادهها را باز گردانند، که در اینصورت نیز ترغیب میشوند در جائیکه فراخواننده نباید از نوع دقیق دادهها اطلاعی داشته باشد، زیر کلاسی از نوع بازگشتی تعریف شده، یا نمونه کلاسی که رابطها را اجرا میکند، بازگردانند.
بدین طریق تولیدکنندگان به راحتی میتوانند در صورت نیاز، کد سرویس را در آینده بدون متوقف کردن سرویس گیرندههای آن، با ایجاد زیر کلاسهای مخصوص نوع بازگشتی، تغییر دهند.
3ـ Garbage collection، به اشتراک گذاشتن دادهها را سادهتر میسازد.
گاهی اوقات برنامههای کاربردی ساخته میشوند که مستلزم به اشتراک گذاردن آبجکت هستند.
مشکلاتی که در تعریف مسئولیتهای clean up وجود دارد از این قرارند: اگر آبجکتA و آبجکتB، pointer C را به اشتراک بگذارند، آیا آبجکتA باید C delete کند یا آبجکتB ؟
مشکل همین حذف کردن است: B,A نباید (و یا نمیتوانند) C را که از C# یا جاوا، استفاده میکند delete کنند.
آبجکت B,A تا زمانیکه لازم باشد از C استفاده میکنند، سپس زمانیکه دیگر ارجاعی صورت نمیگیرد، سیستم مسئول حذف کردنC است.
طبیعتا، تولید کننده نیز هنگام دسترسی به دادههای مشترک باید به قسمتهای اصلی توجه داشته باشد و باید به روش استاندارد آن را اداره نماید.
4 ـ برنامهها باید بطور خودکارصحیحتر شوند.
تولید کنندگان برنامه کاربردی میتوانند بجای مدیریت حافظه، به منطق برنامه و صحت آن توجه و تمرکز کنند تا کدی با اشکالات کمتر ایجاد شود.
این مسئله بسیار حائز اهمیت میباشد.
جاوا و C# هر دو زبانهای Type – Safe هستند Saraswat در صفحه وب خود میگوید:یک زبان در صورتی Type–Safe است که تنها عملیات انجام شده بر روی دادههای آن، از نوع عملیاتی باشد که توسط نوع دادهها تصویب میشوند.
بنابراین، نتیجه میگیریم که طبق این تعریف، C++ ، Type – Safe نیست، حداقل به این دلیل که یک تولید کننده ممکن است نمونهای از برخی کلاسها را به درگیری تبدیل نوع (cast) کند و دادههای نمونه را با استفاده از تبدیل نوع غیر قانونی و متدها و اپراتورهای ناخواسته رونویسی نماید.
جاوا و C# طراحی شدند تا Type–Safe باشند.
یک تبدیل نوع غیر قانونی در صورتیکه غیرقانونی بودن آن نشان داده شود، در زمان کامپایل گیر خواهد افتاد، و یا اینکه اگر Object نتواند به نوع جدید تبدیل شود، در زمان اجرا یک استثناء به وجود خواهد آمد.
بنابراین Type–Safe بودن حائز اهمیت است، زیرا نه تنها تولید کننده را مجبور به نوشتن کد صحیحتر میکند، بلکه به سیستم کمک میکند تا از دست افراد بیتوجه در امان بماند.
جاوا و C# هر دو زبانهای شیءگرا هستند هر کلاسی در هر زبانی تلویحا (یا صریحا) یک Object را به زیر کلاسهایی تقسیم میکند.
این ایده بسیار خوبی است، چرا که در اینصورت یک کلاس پایه پیش فرض برای هر کلاس توکار (built-in) یا تعریف توسط کاربر ارائه میشود.
C++ میتواند تنها با استفاده از اشارهگرهای void این پشتیبانی را شبیهسازی کند، به دلایلی از جمله Type – Safe مسئلهساز هستند.
چرا این افزایش در زبان جاوا و C# خوب است؟
یک دلیل آن اینست که ایجاد محفظههای (container) بسیار عمومی مجاز میشود.
مثلا هر دو زبان دارای کلاسهای پشته از پیش تعریف شده هستند، که به کد برنامه کاربردی اجازه میدهند تا هر Object را به یک نمونه پشته مقداردهی شده push کند، سپس pop را فرا بخواند تا مرجع بالایی Object را حذف و به فراخواننده بازگرداند ـ شبیه تعریف قدیمی پشته است.
طبیعتا، این امر مستلزم آن است که تولید کننده، مرجع حذف شده را به کلاسهای خاص برگرفته از Object تبدیل نوع کند، بگونهای که عملیات معناداری انجام شود، اما در حقیقت نوع تمامی Object هایی که در هر نمونه پشته وجود دارد باید از جانب تولید کننده به زمان کامپایل شناسانده شود حداقل به این دلیل که اگر رابط عمومی کلاس ناشناخته باشد، زمانیکه Object های حذف شده را ارجاع میدهد، اغلب کار کردن با آن Objectها دشوار است.
در C++، اکثر تولیدکنندگان از تطبیق دهنده محفظه stack در Standard Template Library (STL) استفاده میکنند.
برای آنهایی که با STL آشنایی ندارند، Schildt میگوید که STL در اوایل سال 1990 توسط Alexander Stepanov طراحی شد و در سال (5) 1994 توسط انجمن ANSI C++ مورد تصویب واقع شده و در دسترس اکثر کامپایلرهای تجاری C++ و امروزه IDE ها، از جمله eMbdded Visual Tools.NET قرار گرفت.
بخصوص اینکه، STL که مجموعهای از محفظهها، تطبیق دهندههای محفظه، الگوریتمها و غیره میباشد، تولید برنامه کاربردی C++ را آسان میسازد به این ترتیب که به هر کلاس C++ (و ابتدائیترین آنها) اجازه میدهد از طریق تعریف یک الگوسازی عمومی، ساخته و درج شود.
بهرحال، در مورد الگوسازی در C++ مسائلی وجود دارد.
مثلا پشته جدید int ها بدین طریق ایجاد میشود: # include using namespace std; stack intstack; تعریف الگوسازی مشخص شد، پس تعریف واقعی کلاس پشته int و کد اجرایی با استفاده از آن الگوسازی، در پشت پرده ایجاد میشود.
به این ترتیب طبیعتا کدهای بیشتری به فایل اجرایی اضافه میگردد.
حافظه و درایو دیسک سخت امروزه ارزان است.
اما اگر انواع مختلف الگوسازیها توسط یک تولید کننده استفاده شود، مسئلهساز میگردد.
یک پشته باید دارای یک constructor یک destructor ، یک push، یک pop و شاید یک متد size و یا Boolean empty باشد.
به چیز دیگری نیز نیاز ندارد.
و تا زمانیکه آن نوع یک Object است، به دیگر نوعهای موجود توجه نمیکند.
بهرحال، پشته STL به این روش کار نمیکند.
این کار مستلزم دانستن زمان کامپایل نوعی است که آن را نگاه میدارد (تعریف الگوسازی مهم نیست، اما تعریف کامپایلر لازم میباشد).
و پشته STL شامل مجموعهایی از توابع پشته classic نیست.
برای مثال، pop یک تابع void است، تولید کننده ابتدا باید top را فرا بخواند، که در این صورت یک آدرس به بالای پشته باز میگردد، سپس pop را فرا بخواند، که این بدان معنی است که یک عمل هم اکنون به دو عمل تبدیل شده است.
مشکل وراثت در C++ به احتمال زیاد موجب این تصمیم میشود: اگر تابع pop عنصر را بازگرداند و آن را از stack حذف کند، باید یک کپی را بازگرداند(آدرس آن عنصر دیگر معتبر نیست).
سپس اگر فراخواننده پس از بازرسی آن عنصر را نخواهد، باید کپی را به push , stack کند.
مجموعه این عملیات روند کندتری دارد، بخصوص اگر نوع stack کاملا بزرگ باشد.
بنابراین یک متد بازرسی top اضافه شد که هیچگونه تاثیر جانبی بر تعداد عناصر stack نداشت، و به تولیدکننده اجازه میداد تا قبل از حذف یک نمونه کلاس آن را peek کند.
اما هنگامیکه یک تولید کننده به عنصر top دسترسی مییابد، برخی توابع را در آن فرا میخواند، وی ممکن است یک عنصر داخل محفظه (container) را side-effect کند، که حداقل در برخی سناریوها ممکن است روش خوبی نباشد.
معماری اصلی STL ممکن است از تولید کنندگان بخواهد تنها از اشارهگرها یا محفظهها استفاده کنند (در حقیقت، اشارهگرها مجازند، اما شما همچنان باید مدیریت حافظه را در نظر بگیرید)، که ممکن است تغییر الگوسازی را تحت تاثیر قرار دهد و موجب شود متد pop، یک اشارهگر را حذف و به عنصر بالایی بازگرداند، اما مسئله فقدان garbage collection مجددا مطرح میشود.
به نظر میرسد با وجود محدودیتها و مشکلاتی که به C++ به ارث رسیده، لازم است تعریف عمومی STL , Stack از دیدگاه کلاسیک تغییر کند که چنین چیزی اصلا خوب نیست.
تنها مزیت قابل بحث در C++ با استفاده از الگوسازیهای پشته در مقایسه با الگوسازیهای پشتهC# و جاوا از این قرار است: در متد الگوی top، نیاز به هیچگونه تبدیل نوع نیست، زیرا زمان کامپایلی که کلاس stack را ایجاد کرده دارای اطلاعاتی درباره نوع آدرسی است که باید حفظ کند.
اما اگر نظریه بالا تولید کننده را متقاعد کند که وی حتما باید بداند از چه نوعی در نمونه stack مختص خود استفاده کند، این مسئله اهمیت کمتری مییابد.
در عوض، رابطهای عمومی مربوط به کلاسهای stack در هر دو زبان جاوا و C# از روش کلاسیک پیروی میکنند: push دادن یک Object به Stack، که در این صورت Object در بالا قرار میگیرد، سپس pop کردن stack، که عنصر بالایی را حذف و باز میگرداند.
چرا جاوا C# قادر به انجام این کار هستند؟
به این دلیل که آنها هر دو زبان OOP هستند، و شاید مهمتر اینکه، آنها با استفاده از مرجعها از garbage collection پشتیبانی میکنند.
کد Stack میتواند بسیار جدی باشد، چرا که میداند سرویس گیرنده نباید cleanup را اداره کند.
و یک Stack میتواند هر Object ای را نگاه دارد.
دلایل زیاد دیگری وجود دارد که چرا ترجیحا باید از زبان شیءگرای محض ـ توسعهپذیری برنامه کاربردی، مدلسازی جهان واقعی و غیره استفاده کرد.
اما چه چیزی یک زبان شیءگرای محض را تعریف میکند: ـ زبانی که ایجاد انواع تعریف شده توسط کاربر را که معمولا یک کلاس نام دارد، مجاز بداند ـ زبانی که گسترش کلاسها را از طریق ارثبری و یا استفاده از یک رابط مجاز بداند ـ تمام نوعهای ایجاد شده توسط کاربر بطور تلویحی زیر کلاس برخی از کلاسهای پایه باشند، که معمولا Object خوانده میشود.
ـ زبانی که اجازه دهد متدها در کلاسهای مشتق شده، متدهای کلاس پایه را override کنند ـ زبانی که اجازه دهد یک نمونه کلاس به یک کلاس خاصتر یا معمولیتر تبدیل شود ـ زبانی که اجازه دهد ترازهای امنیتی دادههای کلاس، که معمولا به صورت public تعریف میشوند، به صورت private , protected تغییر یابند.
ـ باید overload کردن اپراتور را مجاز بداند ـ نباید فراخوانیهای عمومی تابع را مجاز بداند یا باید آن را بسیار محدود کند ـ در برخی کلاسها تابع باید بیشتر متد یا نمونه رابط باشد.
ـ هر نوع (type) دارای داده و مجموعه عملیاتی است که میتواند در آن نوع اجرا شوند ـ باید type-safe باشد ـ دارای تواناییهای خوبی در مدیریت حالت استثناءها باشد.
ـ آرایهها باید Object های first-class باشند: یک تولید کننده باید قادر به پرس و جوی یک Object در اندازه خود و نوعی باشد که آن را نگاه خواهد داشت.
از آنجائیکه C++ از ابتدا طوری طراحی شده که سازگار باC باشد، در برنامهنویسی شیءگرایی محض سریعا ناسازگاری نشان خواهد داد، زیرا استفاده از فراخوانیهای عمومی تابع را حداقل در صورت استفاده از نیازمندیها شرح داده شده فوق، مجاز میداند.
و آرایهها در C++ ، Objectهای first-class نیستند که این امر موجب نگرانی تولیدکنندگان شده است.
طبیعتا، اگر یک تولید کننده با ایجاد لفافههای آرایه، استفاده از وراثت، دوری از فراخوانیهای عمومی تابع و غیره از زیرمجموعهای از مجموعه مشخصات C++ استفاده کند، کد مخصوص C++ وی را میتوان شیءگرا نامید.
اما از آنجائیکه C++ به شما اجازه انجام کارهایی را میدهد که در تعریف زبان شیءگرای محض مجاز به انجام آنها نیستنید، بهتر است آن را هیبرید بنامید.
بنظر میرسد C# و جاوا معیارهای فوق را برآورده میسازند، بنابراین میتوان گفت که هر دوی آنها زبانهای برنامهنویسی شیءگرای محض هستند.
به نظر میرسد که تنها تجربه در برنامهنویسی است که به شما میگوید که زبانی واقعا شیءگرا است یا خیر، نه ضرورتا بر اساس یکسری نیازمندیهای سخت و محکم.
تک وراثتی C# و جاوا تنها یک وراثت را مجاز میدانند.
در هر دو زبان، هر کلاسی مجاز به اجرای هر تعداد رابطی است که نیاز دارد.
یک رابط چیست؟
رابط شبیه یک کلاس است ـ رابط دارای مجموعه متدهایی است که میتوانند در هر نمونه کلاسی که آنها را اجرا میکند فراخونی شوند ـ اما رابط از دادهها استفاده نمیکند و تنها یک تعریف است.
هر کلاسی که یک رابط را اجرا میکند باید بازای تمام متدها یا خصوصیات تعریف شده توسط رابط، تمام کد اجرایی را بکار بگیرد.
بنابراین، یک رابط بسیار شبیه یک کلاس در C++ است که دارای تمام توابع مجازی محض میباشد (متدهای یک constructor خصوصی یا محافظت شده و یک destructor عمومی که عملکرد جالبی را ارائه نمیدهد) و از دادههای اضافی استفاده نمیکند.
چرا این دو زبان همانند C++ از وراثت چندگانه پشتیبانی نمیکنند؟
Lippman وراثت را به صورت سلسله مراتبی در نظر میگیرد و آن را به صورت نمودار مستقیم غیر مدور (DAG) شرح میدهد، بگونهای که تعریف هر کلاس از طریق یک گره نشان داده میشود و برای هر رابطه پایه به فرزند مستقیم (base-to-direct-child) یک لبه (edge) وجود دارد.
بنابراین، مثال زیر سلسله مراتب یک پاندا در باغوحش را نشان میدهد.
چه چیزی در این شکل اشتباه است؟
تا زمانیکه تنها یک وراثت پشتیبانی میشود، هیچ اشکالی وجود ندارد.
در مورد تک وراثتی، بدون در نظر گرفتن فاصله، تنها یک مسیر از هر کلاس پایه به هر کلاس مشتق شده وجود دارد.
در وراثت چندگانه، وجود دارد، بازای هر گره، مجموعهای از حداقل دو کلاس پایه وجود دارد که یک پایه از خودشان را به اشتراک میگذارند.
بنابراین مثال Lippman به این طریق به نمایش در میآید: در این مورد، استفاده صریح از کلاسهای پایه virtual برای یک تولید کننده عادی است، بگونهای که تنها یک بلوک حافظه بدون توجه به تعداد دفعاتی که در نمودار وراثت به نمایش در میآید، بازای هر نمونهسازی کلاس ایجاد میشود.
اما این کار مستلزم آن است که تولیدکننده اطلاعات خوبی از سلسله مراتب وراثتی داشته باشد و این تضمین نمیشود، مگر آنکه وی نمودار را بازرسی کند و کد خود را به درستی برای هر کلاسی که میسازد یادداشت نماید.
در حالیکه تولید کننده موظف است هرگونه ابهامی را با استفاده از این طرح حل نماید، با این وجود، این طرح میتواند موجب سردرگمی و کد نادرست گردد.
در شیوه تک وراثتی، چند رابطی (single-inheritance-multiple-interface)، این مشکل روی نمیدهد.
در حالیکه این امکان وجود دارد که یک کلاس زیرکلاس دو یا چند کلاس پایه باشد، در صورتیکه هر پایه رابط مشترکی را اجرا کند، به زیرکلاس تقسیم کند،(اگر کلاس One زیرکلاس کلاس Two باشد، و Tow رابط I را اجرا نماید، One نیز I را تلویحا اجرا میکند)، از آنجائیکه رابطها نمیتوانند از دادههای اضافی استفاده کنند و نمیتوانند هر اجرایی را ارائه دهند، این مسئله حائز اهمیت نیست.
در این مورد، هیچ حافظه اضافی را نمیتوان اختصاص داد و در مورد آن نسخه متد virtual که باید فراخوانی شود، هیچ ابهامی وجود ندارد چرا که تک وراثتی است.
وراثت چندگانه خوب است، زیرا در برخی کلاسهای پایه که میتوانند توسط زیرکلاسها بکار روند یا مورد استفاده مجدد قرار بگیرند، تولید کننده ممکن است تنها یکبار کد را بنویسد.
در تک وراثتی،اگر تولیدکننده کلاسی بخواهد که دارای رفتار کلاسهای چندگانه نامرتبط باشد، لازم است کد را تکرار کند.
یک workaround در تک وراثتی، استفاده از رابطها و سپس ایجاد یک کلاس اجرایی جداگانه است که در واقع عملکردی را برای رفتار مطلوب ارائه دهد، و توابع اجراکننده را در هنگام فراخوانی یک رابط، فرا بخواند.
این بحث که تک وراثتی با رابطها بهتر از وراثت چندگانه محض است چرا که تک وراثتی خطاهای منطقی را کم میکند، گفته نادرستی است، زیرا تولیدکننده C++ میتواند با استفاده از کلاسهای مجازی محض که هیچ دادهایی را ارائه نمیدهند، رابطها را شبیهسازی کند.
انعطاف پذیری C++ کمی بیشتر است.
عکس گفته فوق نیز صحیح نیست.
زیرا تولید کننده C# یا جاوا میتواند توسط متدهایی که در بالا بحث شد، وراثت چندگانه را شبیهسازی کند.
بنابراین، میتوان گفت که هر دو طرح برابرند.
Thread توکار و پشتیبانی همگام سازی زبانهایی نظیر ANSI C++ هیچگونه پشتیبانی جهت threading توکار و پشتیبانی از همگامسازی ارائه نمیدهند.
پکیجهای شرکتهای ثالث که از این عملکرد استفاده میکنند باید بر اساس سیستم عامل خریداری شوند، و APIها از پکیجی به پکیج دیگر متفاوتند.
در حالیکه ابزارهای تولید نظیر Visual Studio 6 از API جهت thread کردن C++ استفاده میکنند، این API ها قال حمل نیستند، و معمولا تنها میتوانند در برخی سیستم عاملهای مایکروسافت بکار روند.
بهرحال، C# و جاوا هر دو در مجموعه مشخصات خود دارای پشتیبانی توکار جهت این عملکرد هستند.
این مسئله حائز اهمیت است چرا که به تولید کننده اجازه میدهد تا برنامههای کاربردی multi-thread شده را که به سرعت قابل حمل هستند، ایجاد نماید.
معمولا ایجاد یک برنامه کاربردی قابل حمل و single-thread شده، C++ آسان است.
امروزه اکثر برنامههای کاربردی multi-thread شده هستند یا اگر نیستند، باید باشند.
اما در جاوا و C#، تولید کننده میتواند از thread توکار زبان و توابع همگامسازی استفاده نماید و بسیار احساس امنیت کند که برنامهها به یک روش مشابه در بسترهای مختلف اجرا خواهند شد یا حداقل آنقدر شبیه هستند که درست بودنش را تضمین کنند.
رسیدگی رسمی به مدیریت حالت استثناء رسیدگی رسمی به مدیریت حالت استثناء طی شرایط استثنایی زمان اجرا یک کنترل روند برنامه در اختیار تولیدکننده میگذارد.
این کنترل به همراه توانایی throw کردن یک استثناء از یک تابع، در صورت پیش آمدن هر چیز نامناسب در هنگام اجرا است.
همچنین هر برنامه کاربردی که این تابع را فرا میخواند، این توانایی را دارد تا یک خطای بالقوه را catch ,try کند و finally به اختیار پس از فراخوانی متد کاری را انجام دهد ـ مهم نیست چه کاری.
هنگامیکه یک تابع، استثنائی را throw میکند، هیچ کدی در تابع throw شده اجرا نمیشود.
هنگامیکه یک سرویس گیرنده استثناء را مدیریت میکند، هیچ کدی در بلوک try از یک عبارت try-catch [finally] (TCF) اجرا نمیشود.
نمونه C# زیر برخی اصول اولیه را نشان میدهد: using System; namespace Lame Joke { // Stones is an instance of an Exception public class Stones : Exception { public Stones (string s) : base (s) { } } //All instances of people are poorly behaved – Creation is a failure public class people { /** * Throws a new Stones Exception.
* constructor * / public people ( ) { } } //All Glass Houses fail during construction public class Glass Houses { //private date member.
Private people m – people = null; //Throws an Exception because all people throw Stones public Glass House ( ) { m-people = new people ( ); } } // Main function static void ( ) { Glass Houses glass Houses = null; // try-catch-finally block // try – always executed //catch – executed only if a Stones exception thrown during try // finally always executed try { glass Houses = new Glass Houses ( ); } catch (Stones s) { //This block is executed only if all people are poorly //behaved } finally { } // glasshouses is still null since it filed to be constructed } } جاوا و C# همانند C++، از رسیدگی رسمی مدیریت حالت استثناء پشتیبانی میکنند.
چرا نیاز به مدیریت حالت استثناء احساس میشود؟
زبانهایی وجود دارند که از این پشتیبانی برخوردار نیستند، و تولید کنندگان قادرند با این زبانها کدی بنویسند که به درستی کار کند.
اما صرف اینکه چیزی کار میکند، به این معنی نیست که ضرورتاً خوب است.
ایجاد توابع با استفاده از رسیدگی رسمی به مدیریت حالت استثناء میتواند تا حد زیادی پیچیدگی کد را در client side, server side کاهش دهد.
بدون استثناءها، توابع باید در موردی که پیش شرطها جواب نمیدهند، بجای یک مقدار معتبر، مقادیر نامعتبری را تعریف و بازگردانند.
از آنجائیکه تعریف یک مقدار نامعتبر ممکن است حداقل یک آیتم معتبر را در محدوده تابع حذف کند، این امر میتواند مسئلهساز باشد و میتواند موجب درهم ریختگی شود، زیرا سرویس گیرنده باید مقدار بازگشتی را در برابر برخی مقادیر نامعتبر از پیش تعریف شده، کنترل نماید.
(راهحلهای دیگری که آزمایش شدهاند: 1) افزودن یک ارجاع اضافی non-const Boolean به هر فراخوانی تابع، و مجبور کردن متد برای True قرار دادن آن در صورت موفقیت و در غیر اینصورت false قرار دادن آن 2) قرار دادن یک پارامتر سراسری، حداقل بازای فراخوانی محتوای thread، که آخرین خطایی که یک سرویس دهنده میتواند پس از فراخواندن تابع آزمایش کند را تعریف مینماید.
این راهحلها رضایت بخش نیستند، و ممکن است لازم باشد تولیدکننده برنامه کاربردی اطلاعات بسیار زیادی درباره چگونگی کار کردن چیزهای مختلف داشته باشد.) به کلاس System.
Collection.
Stack که در NET Framework.
مایکروسافت بکار رفته یا به کلاس Java .util .Stack جاوا که بسیار شبیه آن است نگاه کنید.
به نظر میرسد هر دو به طور منطقی طراحی شده باشند: یک متد void Push (Object) و یک متد Object Pop ( ).
تابع دوم در حالتیکه یک نمونه Stack خالی فراخوانده شود، یک Exception ایجاد میکند، که صحیح به نظر میرسد.
گزینه منطقی دیگر، بازگرداندن null است، اما این امر موجب بهم ریختگی میشود، زیرا مستلزم آن است که تولید کننده صحت و سقم یک عمل نامعتبر را آزمایش کند، زیرا به این معناست که pop حداقل یک بار بیشتر از push فراخوانده میشود.
با اجرا و الگوسازی stack متعلق به NET.
که با قوانین رسیدگی به مدیریت استثناء در C# مرتبط هستند، optionهای متعددی وجود دارد (در C#، اگر تولیدکننده از تمام پیش شرطهایی که اجرا شدهاند آگاه باشد، دیگر نیاز به استثناء نیست): Stack s = new Stack ( ); s .push (1); int p = (int)s.
pop ( ); در حالت قبلی، تولید کننده میداند که یک exception نمیتواند از متد pop ایجاد شود، زیرا فراخوانی Push قبلا در یک Object معتبر انجام شده است و هیچ Pop ایی هنوز اجرا نشده است.
بنابراین تولیدکننده Correctly را انتخاب میکند تا از عبارت TCF اجتناب کند.
در مقایسه: try { Stack s = new Stack ( ); s.
push (1); int p = s.
Pop ( ); p = s.
Pop ( ); } catch (Invalid Operation Exception i) { // note: this block will be executed immediately after the second Pop } در حالت بعدی، بنا به دلایلی تولید کننده متقاعد نشده است که Push حداقل به تعداد Pop در Stack فراخوانی شده است، بنابراین تصمیم میگیرد تا نوع Exception ایجاد شده را catch کند.
او عاقلانه عمل کرده استو بهرحال، در حقیقت، یک سرویس گیرنده واقعا باید مطمئن باشد که Pop را بیشتر از Push فراخوانی نکرده است، و نباید صحت و سقم آن را با catch کردن برخی Exceptionها انجام دهد.
اما کد Stack نمیتواند تضمین کند که برنامه کاربردی را که از آن استفاده میکند، به درستی عمل خواهد کرد.
بنابراین اگر برنامه کاربردی ضعیف عمل کند، Stack میتواند تنها برخی Exceptionها را ایجاد نماید.
اگر رسیدگی به مدیریت حالت استثناء بکار برده نشود یا در دسترس نباشد، کد stack، در صورتیکه سرویس گیرنده سعی در Pop کردن یک Stack خالی را داشته باشد، چه چیزی را باید بازگرداند؟
null را؟
پاسخ کاملا واضح نیست.
در برخی زبانها نظیر C++، جائیکه کپی دادهها از توابع موجود در اکثر اجراها بازگردانده میشود، NULL حتی یک Option هم نیست.
با ایجاد یک Exception، کد Stack و سرویس گیرندههای آن نباید بر سر برخی مقادیر return که خارج از محدوده مقادیر پذیرفته شده هستند مجادله کنند.
متد Pop متعلق به Stack، تنها میتواند با ایجاد یک استثناء به مجرد برآورده نشدن برخی پیش شرطها، خارج شود و به کد موجود در اولین بلوک catch در Client Side که نوع قابل تبدیل به استثناء ایجاد شده را کنترل میکند، وارد خواهد شد.
بله، حتی رسیدگی به مدیریت حالت استثناء نیز از روش شیءگرا استفاده میکند.
کدهای داخل یک عبارت TCF به خوبی با یکدیگر کار میکنند، گویا مجموعه نهفتهای از پیش شرطها وجود دارد که میگوید در موردی که خطهای قبلی موجب ایجاد برخی استثناءها میشوند، خط بعدی در بلوک اجرا نخواهد شد.
این امر موجب سادهتر شدن منطق میشود به گونهای که دیگر نیاز نیست سرویس گیرنده شرایط خطا را قبل از اجرای هر خط از کد در بلوک، آزمایش کند.
و کد کلاس میتواند با ایجاد یک استثناء در صورت پیش آمدن مشکل، سریعا از متد خارج شود، و این استثناء نشان میدهد که هیچ return item معتبر وجود نخواهد داشت.