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

-= چه چیزی دربارهء کلاسها آنقدر مفید است؟ =-

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

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

یک کلاس خوب طراحی شده، یک رابط تمیز و ساده را در اختیار کاربرانش میگذارد که پیاده سازی آن را پنهان کرده و کاربرانش را از اجبار برای دانستن درمورد پیاده سازی نجات میدهد. اگر پیاده سازی نباید پنهان باشد، یعنی کاربران باید بتوانند هر عضو داده ای را به هر شکلی که میخواهند تغییر دهند، شما میتوانید از آن کلاس بعنوان یک ساختار داده ای ساده قدیمی (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

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

*

شما می‌توانید از این دستورات HTML استفاده کنید: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>