PDA

View Full Version : دربارهء کلاسها و برنامه نویسی شیء گرا، از زبان ایجاد کنندهء زبان C++‎‎‎



eshpilen
دوشنبه 05 آبان 1393, 00:23 صبح
-= چه چیزی دربارهء کلاسها آنقدر مفید است؟ =-

کلاسها برای کمک کردن به شما جهت سازماندهی کدتان و استدلال کردن دربارهء برنامه هایتان هستند. شما میتوانید تقریبا بگویید که کلاسها برای کمک به شما جهت اجتناب از اشتباهات و کشف کردن باگها بعد از مرتکب اشتباه شدن هستند. به این شکل کلاسها به میزان قابل توجهی به نگهداری کمک میکنند.

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

یک کلاس خوب طراحی شده، یک رابط تمیز و ساده را در اختیار کاربرانش میگذارد که پیاده سازی آن را پنهان کرده و کاربرانش را از اجبار برای دانستن درمورد پیاده سازی نجات میدهد. اگر پیاده سازی نباید پنهان باشد، یعنی کاربران باید بتوانند هر عضو داده ای را به هر شکلی که میخواهند تغییر دهند، شما میتوانید از آن کلاس بعنوان یک ساختار داده ای ساده قدیمی (plain old data structure) فکر کنید؛ برای مثال:

struct Pair {
string name, value;
};

توجه کنید که حتی ساختارهای داده ای میتوانند از توابع کمکی، همچون توابع سازنده، سود ببرند.

وقتی در حال طراحی یک کلاس هستیم، اغلب سودمند است که توجه کنیم چه چیزی برای هر شیء از آن کلاس در تمام اوقات درست است. چنان ویژگی ای یک نامتغییر (invariant) خوانده میشد. برای مثال، نامتغییر یک بردار میتواند آن باشد که پیاده سازی آن از یک اشاره گر به تعدادی عنصر تشکیل شده است و تعداد عناصر در یک عدد صحیح ذخیره شده است. کار هر تابع سازنده (constructor) ایجاد نامتغییرهای کلاس است تا هر متد کلاس بتواند بر روی آن اتکا کند. هر متد کلاس باید در پایان اجرای خود نامتغییر را در یک وضعیت مجاز باقی بگذارد. این روش فکر کردن بخصوص برای کلاسهایی که منابع را، همچون قفل ها، سوکت ها، و فایل ها، مدیریت میکنند مفید است. برای مثال، یک کلاس file handle نامتغییری که یک اشاره گر به یک فایل باز است را نگهداری میکند. تابع سازنده کلاس file handle، فایل را باز میکند. توابع تخریب گر (Destructors) منابعی را که بوسیلهء توابع سازنده بدست آمده اند آزاد میکنند. برای مثال، تابع مخرب یک کلاس file handle فایلی را که بوسیلهء تابع سازنده باز شده بود میبندد:

class File_handle {
public:
File_handle(const char* n, const char* rw)
{ f = fopen(n,rw); if (f==0) throw Open_failure(n); }
~File_handle() { fclose(f); } // destructor
// ...
private:
FILE* f;
};

اگر شما تاکنون با کلاسها برنامه ننوشته باشید، بعضی بخشهای این توضیح را مبهم خواهید یافت و فایدهء کلاسها را دست کم خواهید گرفت. دنبال مثالهایی بگردید. همچون تمام کتابهای خوب، TC++‎‎‎‎PL تعداد زیادی مثال دارد؛ برای نمونه، A Tour of C++‎‎‎‎‎ را ببینید. بیشتر کتابخانه های مدرن C++‎‎‎‎‎، در میان بقیهء چیزها، از کلاسها تشکیل شده اند و خودآموز یک کتابخانه یکی از بهترین مکانها برای گشتن به دنبال مثال هایی از کلاسهای مفید است.

----------------------------------

-= OOP چیست و چرا آنقدر مفید است؟ =-

تعاریف زیادی درمورد «شیء گرا»، «برنامه نویسی شیء گرا»، و «زبانهای برنامه نویسی شیء گرا» وجود دارند. برای یک توضیح طولانی درمورد آنچه که من درمورد «شیء گرا» فکر میکنم Why C++‎‎‎‎ isn't just an object-oriented programming language را بخوانید. برنامه نویسی شیء گرا یک شیوهء برنامه نویسی است که از Simula نشات میگیرد (بیش از 40 سال قبل!) که بر کپسوله سازی، ارث بری، و چند شکلی تکیه دارد. در زمینهء C++‎‎‎‎‎ (و خیلی زبانهای دیگر که ریشه های آنها به Simula برمیگردد)، آن به معنای برنامه نویسی با استفاده از سلسله مراتب کلاسها و توابع virtual برای اجازه دادن دستکاری اشیاء از انواعی گوناگون از طریق رابطهای بخوبی تعریف شده و اجازه دادن به یک برنامه برای گسترش یافتن تدریجی از طریق مشتق شدن است.

بخش «چه چیزی دربارهء کلاسها آنقدر مفید است؟» را برای یک ایده درباره اینکه چه چیزی درمورد «کلاسهای ساده» آنقدر مفید است ببینید. نکته دربارهء چیدمان کلاسها در یک سلسله مراتب کلاس آن است که روابط سلسله مراتبی بین کلاسها را بیان کنیم و آن روابط را برای ساده کردن کد استفاده کنیم.

برای آنکه واقعا OOP را بفهمید، به دنبال مثالهایی بگردید. برای مثال، شما ممکن است دو (یا بیشتر) درایور دستگاه با یک رابط مشترک داشته باشید:

class Driver { // common driver interface
public:
virtual int read(char* p, int n) = 0; // read max n characters from device to p
// return the number of characters read
virtual bool reset() = 0; // reset device
virtual Status check() = 0; // read status
};

این درایور به سادگی یک رابط است. آن هیچ عضو داده ای ندارد و یک مجموعه از توابع virtual دارد. یک درایور میتواند از طریق این رابط استفاده شود و انواع زیادی از درایورها میتوانند این رابط را پیاده سازی کنند:

class Driver1 : public Driver { // a driver
public:
Driver1(Register); // constructor
int read(char*, int n);
bool reset();
Status check();
private:
// implementation details, incl. representation
};

class Driver2 : public Driver { // another driver
public:
Driver2(Register);
int read(char*, int n);
bool reset();
Status check();
private:
// implementation details, incl., representation
};

توجه کنید که این درایورها داده (وضعیت) را نگهداری میکنند و اشیایی از آنها میتوانند ایجاد شوند. آنها توابعی را که در درایور تعریف شده اند پیاده سازی میکنند. ما میتوانیم تصور کنیم که یک درایور به این شکل استفاده شود:

void f(Driver& d) // use driver
{
Status old_status = d.check();
// ...
d.reset();
char buf[512];
int x = d.read(buf,512);
// ...
}
نکتهء کلیدی آن است که تابع f نیازی ندارد بداند چه نوعی از درایور را استفاده میکند؛ تمام چیزی که آن نیاز دارد بداند آن است که یک درایور به آن پاس شده است که به معنای یک رابط برای انواع زیادی از درایورها است. ما میتوانستیم f را مانند این فراخوانی کنیم:

void g()
{
Driver1 d1(Register(0xf00)); // create a Driver1 for device
// with device register at address 0xf00

Driver2 d2(Register(0xa00)); // create a Driver2 for device
// with device register at address 0xa00
// ...
int dev;
cin >> dev;

if (dev==1)
f(d1); // use d1
else
f(d2); // use d2
// ...
}

توجه کنید که وقتی f یک درایور را استفاده میکند، عملیات درست بصورت ضمنی در زمان اجرا انتخاب میشوند. برای مثال، وقتی d1 به f داده میشود، d.read()‎ از Driver1::read()‎ استفاده میکند، درحالیکه وقتی d2 به f داده میشود، d.read()‎ از Driver2::read()‎ استفاده میکند. این گاهی اوقات run-time dispatch یا dynamic dispatch خوانده میشود. در این مورد راهی وجود نداشت که که f بتواند بداند با کدام دستگاه فراخوانی شده است، زیرا ما آن را بر اساس یک ورودی انتخاب کردیم.

لطفا توجه کنید که برنامه نویسی شیء گرا یک نوشدارو نیست. OOP به معنای خوب نیست. اگر روابط سلسله مراتبی ذاتی ای بین مفاهیم بنیادین در مسئلهء شما وجود نداشته باشد، هیچ مقداری از سلسله مراتب و توابع virtual کد شما را بهبود نخواهد داد. قدرت OOP آن است که مسائل زیادی وجود دارند که میتوانند با استفاده از سلسله مراتب کلاسها بیان شوند. ضعف اصلی OOP آن است که تعداد بیش از حدی از افراد تلاش میکنند تا تعداد بیش از حدی از مسائل را در یک قالب سلسله مراتبی درآورند. هر برنامه ای نباید شیء گرا باشد. بعنوان جایگزین ها، استفاده از کلاسهای ساده، برنامه نویسی generic، و توابع مستقل (همچون در زبانهایی مانند math، C، و Fortran) را مورد توجه قرار دهید.

--------------------------------------

-= برنامه نویسی generic چیست و چرا آنقدر مفید است؟ =-

برنامه نویسی generic برنامه نویسی بر اساس پارامتری کردن است. شما میتوانید یک نوع را با نوع دیگری پارامتری کنید (همچون یک بردار با نوع عناصر آن) و یک الگوریتم را با یک الگوریتم دیگر (همچون یک تابع مرتب سازی با یک تابع مقایسه). هدف برنامه نویسی generic عمومی کردن یک الگوریتم یا ساختار داده مفید تا عمومی ترین و مفیدترین شکل آن است. برای مثال، یک بردار از اعداد صحیح خوب است و یک تابع که بزرگترین مقدار را در میان یک بردار از اعداد صحیح پیدا میکند خوب است. اما یک راه حل عمومی که یک بردار از هر نوعی را که کاربر میخواهد فراهم میکند و تابعی که بزرگترین مقدار در هر برداری را پیدا میکند بازهم بهتر است:

vector<string>::iterator p = find(vs.begin(), vs.end(), "Grail");

vector<int>::iterator q = find(vi.begin(), vi.end(), 42);

این مثالها از STL (کتابخانهء استاندارد C++‎‎‎‎‎) هستند. برای یک معرفی مختصر، A Tour of C++‎‎‎‎‎ از TC++‎‎‎‎PL را ببینید.

برنامه نویسی Generic از بعضی جهات از برنامه نویسی شیء گرا منعطف تر است. بخصوص، آن به سلسله مراتب ها وابسته نیست. برای مثال، هیچ رابطهء سلسله مراتبی بین یک عدد صحیح و یک رشته وجود ندارد. برنامه نویسی Generic عموما از OOP ساخت یافته تر است؛ در واقع، یک اصطلاح معمول که برای توصیف برنامه نویسی Generic استفاده میشود «چند شکلی پارامتری» است، و «چند شکلی تک کاره» اصطلاحی است که در مقابل برای برنامه نویسی شیء گرا استفاده میشود. در زمینهء C++‎‎‎‎‎، برنامه نویسی Generic همهء نامها را در زمان کامپایل جایگزین میکند؛ آن درگیر با run-time dispatch نیست. این باعث شده است که برنامه نویسی Generic در حیطه هایی که پرفورمنس زمان اجرا مهم است غالب شود.

لطفا توجه کنید که برنامه نویسی Generic یک نوشدارو نیست. بخشهای زیادی از یک برنامه هستند که به پارامتری شدن نیازی ندارند و مثالهای زیادی که run-time dispatch (برنامه نویسی شیء گرا) نیاز است.

پانوشت مترجم:
این مطالب رو ترجمه و درج کردم چون توضیحات و مثالهای پایه خوبی درمورد مزایای استفاده از کلاسها و شیء گرایی میده، و همچنین این نکته که بین صرف استفاده از کلاس ها و OOP تفاوت قائل میشه (در عین اینکه هر دو رو دارای فواید بزرگی میدونه - یعنی نیازی نیست هر چیزی و هر برنامه ای حتما شیء گرا باشه تا از کلاسها استفاده کنه و سود ببره) و تفاوت بین کلاسهای ساده و شیء گرایی (استفاده از کلاسهای سلسله مراتبی و ویژگیهایی مثل ارث بری و غیره) رو روشن میکنه، و نکتهء دیگر اینکه این اشتباه رو متداول میدونه و روشن میکنه که نباید سعی کرد بیش از حد همه چیز رو شیء گرا کرد و سعی کرد به زور در قالب کلاسهای سلسله مراتبی آورد و میگه که در این موارد میشه از روشهای جایگزین مثل کلاسهای ساده، برنامه نویسی generic، و توابع عادی و مستقل (خارج از کلاسها و اشیاء) استفاده کرد. طبیعتا میشه نتیجه گرفت که بهترین روش کدنویسی لزوما اونی نیست که مثلا همه چیز درش به شکل OOP نوشته شده، بلکه روش بهینه و کد خوب میتونه اونی باشه که درش ترکیبی از تمام متدهای مورد نیاز استفاده شده و ممکنه بخشهایی از اون شیء گرا باشه، بخشهایی از کلاسهای ساده و مستقل بدون هیچ سلسله مراتب و ارث بری استفاده شده باشه، و حتی بخشهایی هم از توابع ساده و مستقل (خارج از کلاسها) تشکیل شده باشه، و همچنین ساختارهای داده ای و توابع و الگوریتم های Generic (اگر C++‎‎‎‎‎ خونده باشید باید از قبل بدونید چیه) در صورتیکه زبان مورد نظر اون رو ساپورت بکنه. البته نمیگم که در هر برنامه ای باید تمام این روشها استفاده بشن، بلکه میگم که منعی نداره و هیچ روشی کاملا کلی و برای همه چیز و هر شرایطی نیست و هیچ اشکالی نداره و میشه کدهای بخشهای مختلف یک برنامه ترکیبی از چند متد برنامه نویسی باشه. اما اگر دقت کنیم شاید مثالهای زیادی رو از برنامه نویسان و برنامه هایی ببینیم که متوجه این موضوع نبودن و مثلا سعی کردن همه چیز رو در قالب OOP دربیارن چون فکر میکردن که OOP یعنی پیشرفته و خوب و میشه و باید همه چیز و تمام کد رو بصورت شیء گرا نوشت. البته اینم بگم که بنده خودم در زمینهء OOP تجربهء عملی خیلی کمی دارم و نمیتونم شخصا اظهار نظر تجربی بکنم، ولی از نظر این توضیحات و منبع و شخص شناخته شده و معتبری که پشتش هست و مواردی که شخصا دیدم و تحلیل و استنباطی که دارم، این مطالب و بیان این نکته ها رو مفید دیدم و البته قابل بحث و تبادل نظر هم هست.

====================

منبع: http://www.stroustrup.com/bs_faq.html#class

MMSHFE
دوشنبه 05 آبان 1393, 09:49 صبح
یکی از امتیازهای اصلی PHP نسبت به ASPX رو هم همیشه همین موضوع میدونستم که شئ گرایی در PHP یک قابلیته نه یک اجبار و میشه جاهایی از برنامه رو رویه گرا کار کرد (توی NET. نمیشه).

eshpilen
دوشنبه 05 آبان 1393, 10:23 صبح
توی NET. نمیشه.
مطمئنید؟
چرا نمیشه؟
یعنی توی دات نت نمیشه توابع بدون کلاس تعریف و استفاده کرد؟

Veteran
دوشنبه 05 آبان 1393, 10:46 صبح
مطمئنید؟
چرا نمیشه؟
یعنی توی دات نت نمیشه توابع بدون کلاس تعریف و استفاده کرد؟

نه! هرجا کد بزنی،اونجا باز خودش توی ی کلاس و NAMESPACE و توابع شما در قالب متد تعریف میشن :متفکر:

MMSHFE
دوشنبه 05 آبان 1393, 11:36 صبح
دقیقاً همینطوره. برای مثال خود فرمی که داخلش کد مینویسین یک کلاسه. صفحه وب یک کلاسه و الی آخر.

Veteran
دوشنبه 05 آبان 1393, 11:44 صبح
جدای از این مسائل جناب شهرکی، کلا نمیدونم چرا وقتی وارد محیط NET. میشم انگار وحی الهی میشه که پایدار نیست، هرلحظه ممکنه مشکلی میش بیاد و بنظر خیلی کند هست و سخت اعزار قوی برای اجرای محیط توسعه و حتی نرم افزار نهایی رو میخواد!
و در این 7سال که من سیستم دارم و توی اینترنت هستم نرم افزار های خیلی کمی دیدم که از با NET. نوشته شده باشن.چون عادت دارم بعد نصب نرم افزاری با Api های خود ویندوز className یک بخش از نرم افزار رو چک کنم.
خیلی کم پیش اومده با نرم فزاری مواجه بشم که از گروه NET. باشه! زیاد نمیشه روی اون حساب کرد! چه تجاری چه شخصی و چه بین المللی!

MMSHFE
دوشنبه 05 آبان 1393, 11:52 صبح
اکثر نرم افزارهای بزرگ امروزی با Delphi یا Java نوشته میشه. بگذریم این موضوع ارتباطی به تاپیک نداره.

arash691
دوشنبه 05 آبان 1393, 12:16 عصر
اشتباه دات نت تو برنامه نویسی تحت وب اینه که دید برنامه نویسی تحت وب نداره و مثل تحت دسکتاپ داره کار میکنه برای همینه نیاز به سخت افزار بالایی داره ...

eshpilen
دوشنبه 05 آبان 1393, 12:25 عصر
دقیقاً همینطوره. برای مثال خود فرمی که داخلش کد مینویسین یک کلاسه. صفحه وب یک کلاسه و الی آخر.
این که به خودی خودش مشکلی نیست و تازه میتونه نشانهء یک طراحی شیء گرای کامل و خوب باشه و برنامه نویس هم میتونه ازش به موقع استفاده بکنه و اگر هم نخواست که هیچ. مشکل جایی پیش میاد که مثلا برنامه نویس نمیخواد کلاس تعریف کنه و میخواد یک تابع ساده و مستقل داشته باشه ولی نمیتونه زبان میگه حتما باید بذاری توی یک کلاس! خلاصه یجورایی انتخاب استایل کدنویسی برنامه نویس در سطح کدها و منطق برنامهء خودش رو محدود بکنه. وگرنه خود زبان و فریمورک که واسه خودش ساختار و طراحی و کدهای داخلی داره اون بطور معمول به آزادی برنامه نویس در نوشتن کدها و الگوریتم و طراحی ساختار برنامهء خودش خدشهء جدی وارد نمیکنه.

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

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

در خیلی زبانها ما این رو داریم که پشت پرده تقریبا همه چیز شیء است (فکر کنم یک مثالش جاوااسکریپت باشه). مثلا حتی یک متغییر عدد اعشاری ساده که شما مثلا با عبارت myVar=3.56 ایجادش میکنید ممکنه در یک زبانی بصورت یک شیء ذخیره بشه و متدهایی برای کار با اعداد اعشاری داشته باشه و این حرفا، ولی همونطور که میبینید در سطح کد شما بطور معمول تفاوتی مشاهده نمیکنید و استایل کدنویسی رو تغییر نمیده و تفاوتش در ظاهر فقط جایی مشخص میشه که برنامه نویس از اون متدها و خواص شیء بصورت صریح استفاده میکنه، که اونم بطور معمول مشکلی نیست و تازه خوب هم هست.

MMSHFE
دوشنبه 05 آبان 1393, 12:33 عصر
بطور کلی شما نمیتونید توی NET. یک تابع رو خارج از کلاس تعریف کنید و حتماً باید داخل کلاس باشه (حتی شده static) و این مسئله به نظر من با فلسفه Feature بودن OOP (نه اجباری بودنش) تضاد داره. یه جورایی همون تفاوت بین must و should تو زبان انگلیسی هست که درسته خیلی ظریفه ولی بهرحال تفاوت وجود داره.

eshpilen
دوشنبه 05 آبان 1393, 12:42 عصر
البته اگر اینطور باشه که شما میگی، فکر کنم زیاد جالب نیست و زبانهایی که در این زمینه برنامه نویس رو محدود نمیکنن در این زمینهء خاص مزیت داشته باشن، اما در کل فکر نمیکنم این مطلب خیلی هم مهم باشه (اونقدری که بگیم فقط به این خاطر کسی دات نت رو استفاده نکنه) چون در برنامه نویسی اپلیکیشن و عادی موارد گستردش خیلی زیاد و مهم نیست بنظرم و در اون موارد هم بقول خودتون میشه از یکسری ترفندهایی مثل تعریف یک کلاس خاص بجای فضای گلوبال برنامه و توابع استاتیک و این حرفا بجاش استفاده کرد. بهرحال برنامه نویس بخواد شیء گرایی رو دور بزنه، هیچ زبان و کامپایلر و IDE نمیتونه جلوش رو بگیره، گرچه زحمتش یخورده زیاد و شکل برنامه هم مقداری محدود و غیرعادی میشه.

البته من تجربهء عملی کافی چون ندارم شاید هم اشتباه میکنم، ولی تا اینجا تا جاییکه میدونم و بنظرم میرسه اینطوره.

MMSHFE
دوشنبه 05 آبان 1393, 12:49 عصر
خوب درسته همه چیز راه دور زدن داره و صحبت من هم این نبود که کسی بخاطر این چیزا سمتش نره و فقط گفتم یکی از دلایلی که از NET. خوشم نمیاد همینه که فرضاً شئ گرایی رو یک اجبار گذاشته و برنامه نویس باید دنبال راه دورزدنش باشه که ممکنه اصولی هم نباشه. اینجور سیاستهای اجباری (که بعضاً غیر اصولی هم هستن) زیاد توی سیاستهای مایکروسافت دیده میشه.