جلسه 14 (Generics) داده هاي عام
مقدمه:
در دنیای برنامهنویسی، اغلب با نیاز به نوشتن کدهایی مواجه میشویم که بتوانند با انواع مختلفی از دادهها کار کنند، بدون اینکه مجبور باشیم برای هر نوع داده، کد تکراری بنویسیم. مفاهیم شیءگرایی مانند وراثت تا حدی این مشکل را حل میکنند، اما همیشه انعطافپذیری کامل را ارائه نمیدهند. اینجا است که “دادههای عام” یا Generics وارد صحنه میشوند. Generics به ما این امکان را میدهند که کلاسها، متدها و ساختار دادههایی بسازیم که قادر به کار با انواع دادهای دلخواه باشند و این کار را به شکلی ایمن و با حفظ اطلاعات نوع داده انجام دهند.
چرا به دادههای عام نیاز داریم؟
- کاهش کد تکراری: بدون Generics، برای کار با انواع مختلف داده (مثلاً یک لیست که هم اعداد صحیح و هم رشتهها را نگهداری کند)، مجبور به نوشتن متدهای جداگانه برای هر نوع هستیم.
- افزایش ایمنی نوع داده (Type Safety): Generics اطمینان حاصل میکنند که در زمان کامپایل (Compile-time) خطاهای مربوط به ناسازگاری نوع داده شناسایی شوند، نه در زمان اجرا (Run-time). این امر از بروز بسیاری از باگهای غیرمنتظره جلوگیری میکند.
- قابلیت استفاده مجدد کد: کدهای نوشته شده با Generics بسیار قابل استفاده مجدد هستند و میتوانند در بخشهای مختلف برنامه یا حتی در پروژههای دیگر به راحتی به کار گرفته شوند.
- کارایی: در مقایسه با روشهای قدیمیتر مانند استفاده از
objectیاvoid*در برخی زبانها، Generics معمولاً سربار (Overhead) کمتری دارند زیرا اطلاعات نوع داده در زمان اجرا حفظ میشود.
مفهوم دادههای عام:
دادههای عام به ما اجازه میدهند تا یک “قالب” یا “الگو” برای کلاسها، اینترفیسها و متدها تعریف کنیم که نوع دادهی خاصی به آن اختصاص داده نشده است. این نوع داده در زمان استفاده از آن کلاس، اینترفیس یا متد مشخص میشود. ما معمولاً از یک حرف بزرگ (مانند T برای Type) به عنوان نمادین نوع دادهی عام استفاده میکنیم.
مثال (مفهومی، بدون کد خاص زبانی):
فرض کنید میخواهیم یک کلاس Box بسازیم که بتواند یک شیء را در خود نگه دارد.
- بدون Generics:
textکلاس Box {
شیء محتوا؛
تابع setContent(محتوا: شیء) { این.محتوا = محتوا؛ }
تابع getContent(): شیء { بازگرداندن این.محتوا؛ }
}
// استفاده:
Box myBox = new Box();
myBox.setContent("Hello"); // مشکلی نیست
string s = myBox.getContent(); // نیاز به تبدیل نوع (casting) دارد و ممکن است در زمان اجرا خطا دهد اگر محتوا عدد باشد!
مشکل اینجاست که Box نمیداند چه نوع دادهای را نگه میدارد و ما هنگام بازیابی محتوا نیاز به تبدیل نوع داریم که ایمن نیست.
- با Generics:
textکلاس Box<T> { // T نمایانگر نوع دادهی دلخواه است
T محتوا؛
تابع setContent(محتوا: T) { این.محتوا = محتوا؛ }
تابع getContent(): T { بازگرداندن این.محتوا؛ }
}
// استفاده:
Box<string> stringBox = new Box<string>();
stringBox.setContent("Hello"); // فقط رشته قبول میکند
string s = stringBox.getContent(); // تبدیل نوع لازم نیست، خودکار string است
Box<int> intBox = new Box<int>();
intBox.setContent(123); // فقط عدد صحیح قبول میکند
int i = intBox.getContent(); // تبدیل نوع لازم نیست، خودکار int است
در این حالت، Box<T> یک “قالب” است. وقتی میگوییم Box<string>، در واقع یک کلاس Box مخصوص رشتهها میسازیم. این کار ایمنی نوع داده را تضمین میکند.
کاربردها:
- ساختارهای داده: لیستها (Lists)، صفها (Queues)، پشتهها (Stacks)، دیکشنریها (Dictionaries) و درختها (Trees) همگی میتوانند با Generics پیادهسازی شوند تا با انواع دادههای مختلف کار کنند.
- متدهای کمکی: متدهایی که عملیات خاصی را روی دادهها انجام میدهند (مانند مرتبسازی، جستجو، مقایسه) و نباید وابسته به نوع دادهی خاصی باشند.
- توابع عمومی: توابعی که عملیات کلی را انجام میدهند و نوع دادهی ورودی و خروجی آنها میتواند متغیر باشد.
نکات کلیدی برای تدریس:
- تاکید بر مشکل “کد تکراری” و “ناامنی نوع داده” در رویکردهای بدون Generics.
- معرفی Generics به عنوان راهحلی مدرن و ایمن.
- استفاده از نماد
Tو توضیح اینکه این یک قرارداد است و میتوان از حروف دیگر هم استفاده کرد. - نشان دادن تفاوت بین تعریف کلاس/متد عام و استفاده از آن با یک نوع دادهی مشخص.
- توضیح اینکه Generics در زمان کامپایل اطلاعات نوع را بررسی میکنند (Compile-time type checking).
- اشاره به اینکه در برخی زبانها (مانند جاوا)، Generics ممکن است در زمان اجرا به صورت Type Erasure عمل کنند، اما همچنان مزایای ایمنی نوع داده در زمان کامپایل را حفظ میکنند.
تمرین پایان فصل:
- یک کلاس عام
Pair<T1, T2>طراحی کنید که بتواند دو مقدار از دو نوع دادهی مختلف را نگهداری کند. متدهایی برای تنظیم و دریافت هر یک از مقادیر اضافه کنید. - یک متد عام
swap<T>بنویسید که دو متغیر از یک نوع دادهی یکسان را دریافت کرده و مقادیر آنها را با هم جابجا کند. - چرا استفاده از Generics به جای وراثت برای ایجاد ساختارهای دادهی عمومی (مانند لیست) ارجحیت دارد؟ توضیح دهید.
- یک کلاس عام
Calculator<T>بنویسید که بتواند عملیات پایه (جمع، تفریق) را روی دو مقدار از نوعTانجام دهد. (فرض کنیدTنوعی عددی باشد).
خلاصه نکات کلیدی:
- Generics امکان نوشتن کدهای انعطافپذیر و قابل استفاده مجدد را با انواع دادههای مختلف فراهم میکنند.
- مزایای اصلی: کاهش کد تکراری، افزایش ایمنی نوع داده، و قابلیت استفاده مجدد.
- با استفاده از نمادهایی مانند
T، الگوهایی برای کلاسها، اینترفیسها و متدها تعریف میشوند که نوع دادهی واقعی در زمان استفاده مشخص میگردد. - Generics به شناسایی خطاها در زمان کامپایل کمک شایانی میکنند