الدوال والمعاملات
الدوال والمعاملات في Dart
الدوال هي اللبنات الأساسية لأي تطبيق Dart. فهي تتيح لك تنظيم الكود في أجزاء قابلة لإعادة الاستخدام ومعيارية تؤدي مهامًا محددة. في هذا الدرس، سنستكشف كل شيء من تعريفات الدوال الأساسية إلى المفاهيم المتقدمة مثل الإغلاقات والدوال عالية المستوى.
main() هي دالة من المستوى الأعلى. Dart هي لغة كائنية التوجه حقيقية، لكن الدوال يمكن أن توجد خارج الفئات كدوال من المستوى الأعلى.
تعريف الدوال
يتم تعريف الدالة في Dart بتحديد نوع الإرجاع، واسم الدالة، والمعاملات بين أقواس، وجسم الدالة بين أقواس معقوفة.
// دالة أساسية مع نوع إرجاع
String greet(String name) {
return 'Hello, $name!';
}
// دالة بدون قيمة إرجاع (void)
void printMessage(String message) {
print(message);
}
// استدعاء الدوال
void main() {
String greeting = greet('Alice');
print(greeting); // Hello, Alice!
printMessage('Welcome to Dart!'); // Welcome to Dart!
}
أنواع الإرجاع
كل دالة في Dart لها نوع إرجاع. إذا لم يتم تحديد نوع الإرجاع، فإن Dart تعاملها ضمنيًا على أنها dynamic. تشمل أنواع الإرجاع الشائعة void و String و int و double و bool و List و Map والأنواع المخصصة.
// ترجع عددًا صحيحًا
int add(int a, int b) {
return a + b;
}
// ترجع قيمة منطقية
bool isEven(int number) {
return number % 2 == 0;
}
// ترجع قائمة
List<String> getNames() {
return ['Alice', 'Bob', 'Charlie'];
}
// ترجع خريطة
Map<String, int> getScores() {
return {'Alice': 95, 'Bob': 87, 'Charlie': 92};
}
void main() {
print(add(3, 5)); // 8
print(isEven(4)); // true
print(getNames()); // [Alice, Bob, Charlie]
print(getScores()); // {Alice: 95, Bob: 87, Charlie: 92}
}
المعاملات المطلوبة
بشكل افتراضي، جميع المعاملات الموضعية في Dart مطلوبة. لا يمكن استدعاء الدالة بدون توفير قيم لكل معامل مطلوب.
// كلا المعاملين مطلوبان
double calculateArea(double width, double height) {
return width * height;
}
void main() {
double area = calculateArea(5.0, 3.0);
print('Area: $area'); // Area: 15.0
// calculateArea(5.0); // خطأ: عدد قليل جدًا من المعاملات الموضعية
}
المعاملات الموضعية الاختيارية
يتم وضع المعاملات الموضعية الاختيارية بين أقواس مربعة []. يمكن حذفها عند استدعاء الدالة، وفي هذه الحالة تكون قيمتها الافتراضية null (أو قيمة افتراضية محددة).
String buildGreeting(String name, [String? title, String? suffix]) {
String result = 'Hello, ';
if (title != null) {
result += '$title ';
}
result += name;
if (suffix != null) {
result += ' $suffix';
}
return result;
}
void main() {
print(buildGreeting('Smith')); // Hello, Smith
print(buildGreeting('Smith', 'Dr.')); // Hello, Dr. Smith
print(buildGreeting('Smith', 'Dr.', 'PhD')); // Hello, Dr. Smith PhD
}
المعاملات المسماة الاختيارية
يتم وضع المعاملات المسماة بين أقواس معقوفة {}. وهي اختيارية بشكل افتراضي ويتم الإشارة إليها بالاسم عند استدعاء الدالة. وهذا يحسن قابلية قراءة الكود بشكل كبير.
void createUser({
required String name,
required String email,
int age = 0,
String role = 'user',
}) {
print('Name: $name');
print('Email: $email');
print('Age: $age');
print('Role: $role');
}
void main() {
createUser(
name: 'Alice',
email: 'alice@example.com',
);
// Name: Alice
// Email: alice@example.com
// Age: 0
// Role: user
createUser(
name: 'Bob',
email: 'bob@example.com',
age: 30,
role: 'admin',
);
// Name: Bob
// Email: bob@example.com
// Age: 30
// Role: admin
}
[] والمعاملات المسماة الاختيارية {} في نفس الدالة. يجب عليك اختيار أحدهما.
القيم الافتراضية
يمكن أن تحتوي كل من المعاملات الموضعية والمسماة الاختيارية على قيم افتراضية. إذا لم يقدم المستدعي قيمة، يتم استخدام القيمة الافتراضية.
// قيم افتراضية مع معاملات موضعية اختيارية
String repeat(String text, [int times = 1, String separator = ' ']) {
return List.filled(times, text).join(separator);
}
// قيم افتراضية مع معاملات مسماة
double calculatePrice({
required double basePrice,
double taxRate = 0.10,
double discount = 0.0,
}) {
double taxed = basePrice * (1 + taxRate);
return taxed - discount;
}
void main() {
print(repeat('Hi')); // Hi
print(repeat('Hi', 3)); // Hi Hi Hi
print(repeat('Hi', 3, '-')); // Hi-Hi-Hi
print(calculatePrice(basePrice: 100.0)); // 110.0
print(calculatePrice(basePrice: 100.0, discount: 10.0)); // 100.0
print(calculatePrice(basePrice: 100.0, taxRate: 0.20)); // 120.0
}
الدوال السهمية (=>)
بالنسبة للدوال التي تحتوي على تعبير واحد، توفر Dart صيغة مختصرة باستخدام السهم السميك (=>). يتم تقييم التعبير وإرجاعه تلقائيًا.
// دالة تقليدية
int addTraditional(int a, int b) {
return a + b;
}
// دالة سهمية مكافئة
int addArrow(int a, int b) => a + b;
// دوال سهمية مع أنواع إرجاع مختلفة
bool isAdult(int age) => age >= 18;
String capitalize(String s) => s[0].toUpperCase() + s.substring(1);
double circleArea(double radius) => 3.14159 * radius * radius;
// دالة سهمية ترجع void (تنفذ إجراء)
void sayHello(String name) => print('Hello, $name!');
void main() {
print(addArrow(3, 5)); // 8
print(isAdult(20)); // true
print(capitalize('dart')); // Dart
print(circleArea(5.0)); // 78.53975
sayHello('World'); // Hello, World!
}
الدوال المجهولة (الإغلاقات)
الدوال المجهولة (وتسمى أيضًا دوال لامدا أو الإغلاقات) هي دوال بدون اسم. تُستخدم عادةً كوسائط لدوال أخرى، خاصةً مع عمليات المجموعات.
void main() {
// دالة مجهولة مُعيّنة لمتغير
var multiply = (int a, int b) {
return a * b;
};
print(multiply(4, 5)); // 20
// دالة مجهولة سهمية
var square = (int n) => n * n;
print(square(6)); // 36
// دوال مجهولة مع عمليات القوائم
var numbers = [1, 2, 3, 4, 5];
// forEach مع دالة مجهولة
numbers.forEach((number) {
print('Number: $number');
});
// map مع دالة مجهولة سهمية
var doubled = numbers.map((n) => n * 2).toList();
print(doubled); // [2, 4, 6, 8, 10]
// where (تصفية) مع دالة مجهولة
var evens = numbers.where((n) => n % 2 == 0).toList();
print(evens); // [2, 4]
// reduce مع دالة مجهولة
var sum = numbers.reduce((a, b) => a + b);
print(sum); // 15
}
الإغلاقات والتقاط المتغيرات
الإغلاق هو دالة تلتقط المتغيرات من نطاقها المحيط. يحتفظ الإغلاق بالوصول إلى هذه المتغيرات حتى بعد عودة الدالة المحيطة.
// دالة ترجع إغلاقًا
Function makeCounter() {
int count = 0;
return () {
count++;
return count;
};
}
// إغلاق يلتقط مُضاعِف
Function makeMultiplier(int factor) {
return (int number) => number * factor;
}
void main() {
var counter = makeCounter();
print(counter()); // 1
print(counter()); // 2
print(counter()); // 3
var doubler = makeMultiplier(2);
var tripler = makeMultiplier(3);
print(doubler(5)); // 10
print(tripler(5)); // 15
}
الدوال عالية المستوى
الدالة عالية المستوى هي دالة تأخذ دوالًا أخرى كمعاملات أو ترجع دالة. تدعم Dart الدوال عالية المستوى بشكل كامل، مما يجعلها لغة رائعة للبرمجة بأسلوب وظيفي.
// دالة عالية المستوى تأخذ دالة كمعامل
int applyOperation(int a, int b, int Function(int, int) operation) {
return operation(a, b);
}
// دالة عالية المستوى ترجع دالة
Function createGreeter(String greeting) {
return (String name) => '$greeting, $name!';
}
// استخدام typedef لأنواع الدوال
typedef MathOperation = int Function(int, int);
int calculate(int a, int b, MathOperation op) {
return op(a, b);
}
void main() {
// تمرير دوال كوسائط
print(applyOperation(10, 5, (a, b) => a + b)); // 15
print(applyOperation(10, 5, (a, b) => a - b)); // 5
print(applyOperation(10, 5, (a, b) => a * b)); // 50
// استخدام دالة مُرجعة
var helloGreeter = createGreeter('Hello');
var hiGreeter = createGreeter('Hi');
print(helloGreeter('Alice')); // Hello, Alice!
print(hiGreeter('Bob')); // Hi, Bob!
// استخدام typedef
MathOperation add = (a, b) => a + b;
MathOperation multiply = (a, b) => a * b;
print(calculate(6, 3, add)); // 9
print(calculate(6, 3, multiply)); // 18
}
أساسيات التكرار (Recursion)
التكرار هو عندما تستدعي الدالة نفسها. كل دالة تكرارية تحتاج إلى حالة أساسية (شرط يوقف التكرار) وحالة تكرارية (حيث تستدعي الدالة نفسها بمشكلة أصغر).
// المضروب باستخدام التكرار
int factorial(int n) {
if (n <= 1) return 1; // الحالة الأساسية
return n * factorial(n - 1); // الحالة التكرارية
}
// متتالية فيبوناتشي باستخدام التكرار
int fibonacci(int n) {
if (n <= 0) return 0; // الحالة الأساسية
if (n == 1) return 1; // الحالة الأساسية
return fibonacci(n - 1) + fibonacci(n - 2); // الحالة التكرارية
}
// مجموع قائمة باستخدام التكرار
int sumList(List<int> numbers) {
if (numbers.isEmpty) return 0;
return numbers.first + sumList(numbers.sublist(1));
}
void main() {
print(factorial(5)); // 120 (5 * 4 * 3 * 2 * 1)
print(fibonacci(7)); // 13 (0, 1, 1, 2, 3, 5, 8, 13)
print(sumList([1, 2, 3, 4, 5])); // 15
}
النطاق (Scope)
النطاق يحدد أين يمكن الوصول إلى المتغيرات. تستخدم Dart النطاق المعجمي، مما يعني أن المتغير يكون متاحًا داخل كتلة الكود حيث تم تعريفه، بما في ذلك أي كتل متداخلة.
// نطاق المستوى الأعلى (عام)
String globalVar = 'I am global';
void outerFunction() {
// نطاق محلي
String outerVar = 'I am in outerFunction';
void innerFunction() {
// نطاق محلي متداخل
String innerVar = 'I am in innerFunction';
// يمكن الوصول إلى جميع النطاقات الخارجية
print(globalVar); // I am global
print(outerVar); // I am in outerFunction
print(innerVar); // I am in innerFunction
}
innerFunction();
print(globalVar); // I am global
print(outerVar); // I am in outerFunction
// print(innerVar); // خطأ: innerVar غير متاح هنا
}
void main() {
outerFunction();
print(globalVar); // I am global
// print(outerVar); // خطأ: outerVar غير متاح هنا
// نطاق الكتلة مع if/for
for (int i = 0; i < 3; i++) {
var loopVar = 'Iteration $i';
print(loopVar);
}
// print(loopVar); // خطأ: loopVar غير متاح خارج الحلقة
}
تجميع كل شيء معًا
إليك مثال شامل يجمع بين مفاهيم الدوال المتعددة في سيناريو عملي.
// typedef للوضوح
typedef Validator = bool Function(String);
// دالة عالية المستوى تتحقق من صحة البيانات
List<String> filterValid(List<String> items, Validator validator) {
return items.where(validator).toList();
}
// دوال ترجع مُتحققات (إغلاقات)
Validator minLength(int min) {
return (String value) => value.length >= min;
}
Validator containsChar(String char) {
return (String value) => value.contains(char);
}
// معاملات مسماة مع قيم افتراضية
String formatList(
List<String> items, {
String separator = ', ',
String prefix = '',
String suffix = '',
}) {
return prefix + items.join(separator) + suffix;
}
void main() {
var emails = [
'alice@example.com',
'bob',
'charlie@test.com',
'd@e',
'frank@example.com',
];
// استخدام الإغلاقات كمُتحققات
var validEmails = filterValid(emails, (email) {
return minLength(5)(email) && containsChar('@')(email);
});
print(formatList(
validEmails,
prefix: 'Valid emails: [',
suffix: ']',
));
// Valid emails: [alice@example.com, charlie@test.com, frank@example.com]
}
تمرين تطبيقي
أنشئ الدوال التالية:
- دالة
powerتأخذbase(مطلوب) ومعامل مسمى اختياريexponentبقيمة افتراضية 2. يجب أن تستخدم التكرار لحساب النتيجة. - دالة عالية المستوى
applyToAllتأخذList<int>ودالة، ثم ترجع قائمة جديدة مع تطبيق الدالة على كل عنصر. - دالة
createRangeCheckerتأخذ قيمminوmaxوترجع إغلاقًا يتحقق مما إذا كان رقم معين ضمن هذا النطاق.
الناتج المتوقع:
// power(3) يجب أن ترجع 9 (3^2)
// power(2, exponent: 5) يجب أن ترجع 32 (2^5)
// applyToAll([1, 2, 3], (n) => n * 10) يجب أن ترجع [10, 20, 30]
// var check = createRangeChecker(1, 10);
// check(5) يجب أن ترجع true
// check(15) يجب أن ترجع false