Type Variance: Covariance & Contravariance
مفهوم الـ Type System: متى وكيف نستخدم Covariance و Contravariance في Dart.
The problem
في Dart، لما تحاول تعمل Override لدالة وتغير نوع الـ Parameter لنوع أكثر تحديداً (مثلاً من Animal إلى Dog)، الـ Compiler بيطلع Error لأن باراميترات الدوال contravariant بطبيعتها، ومينفعش الـ Subclass يضيّق النوع مباشرة.
The approach
1. Covariance: بنستخدم كلمة `covariant` في كلاس الـ Parent أو الـ Child عشان نعرّف Dart إن النوع هيتغير بس لازم يكون نوع وارث من الـ Parent. 2. Contravariance: بيسمح لك باستخدام دالة عامة (General) في سياق خاص (Specific Context) — مثل سنتر بيرد على المشاكل العامة يقدر يستقبل ويرد على حالات الطوارئ، بس مش العكس.
abstract class Animal {
final String name;
const Animal(this.name);
}
class Dog extends Animal {
const Dog(super.name);
void bark() => print('Woof!');
}
// 1. Covariance: استخدام نوع أكثر تحديداً بدلاً من نوع عام
abstract class AnimalTrainer {
// استخدام covariant يخبر Dart بالسماح بنوع فرعي وارث من Animal
void train(covariant Animal animal);
}
class DogTrainer extends AnimalTrainer {
@override
void train(Dog animal) {
animal.bark(); // هنا نقدر نستخدم دوال الـ Dog بأمان
}
}
// 2. Contravariance: استخدام دالة عامة في سياق خاص
typedef DogHandler = void Function(Dog dog);
void processDogEvent(Dog dog, DogHandler handler) {
handler(dog);
}
// دالة عامة تتعامل مع أي Animal
void generalAnimalHandler(Animal animal) {
print('Handling animal: ${animal.name}');
}
void main() {
final dog = Dog('Buddy');
// Contravariance: الدالة العامة (Animal -> void)
// مقبولة وتعمل بنجاح في مكان الدالة الخاصة (Dog -> void)
processDogEvent(dog, generalAnimalHandler);
}Reach for it when
- ✔استخدام `covariant` لما كلاس فرعي يحتاج يتعامل حصرياً مع نوع فرعي محدد (مثل DogTrainer يتعامل فقط مع Dog)
- ✔تمرير General Callbacks لمعالجة Specialized Events (Contravariance)
- ✔فهم هيكلة Flutter الداخلية مثل تحديثات الـ RenderObject والـ Widgets
Think twice when
- !لو تقدر تستخدم Generics (`Trainer<T extends Animal>`) لأنها أفضل وأكثر أماناً في وقت الـ Compile time
- !تضييق نوع الـ Parameter مع وجود احتمالية لتمرير نوع فرعي آخر في الـ Runtime
Gotcha: الأفضل دائماً استخدام الـ Generics بدلاً من `covariant` لأن الـ Generics بتضمن الـ Type Safety في وقت الـ Compile time بدون الحاجة لـ Runtime checks.