Dart: ميزات Dart 3 الجديدة
آخر تحديث: 2026-08-26
الميزات الكبرى الثلاث في Dart 3 — ارتقِ بكودك من "يعمل" إلى "أنيق".
1. ما ستتعلمه
- السجلات (Records): تجميع بيانات مجهول خفيف الوزن، قيم إرجاع متعددة
- مطابقة الأنماط: التفكيك / if-case / switch-pattern / فحص شامل
- الفئات المختومة: تسلسل وراثة مقيد + ضمانات switch الشامل
- الثلاثي في العمل: مزيج Sealed + Pattern + Records
- سيناريو بوب: خط أنابيب البيانات يستخدم الفئات المختومة لتعريف أنواع مصادر البيانات، ومطابقة الأنماط لتحليل النتائج
2. قصة مطور حقيقي
(1) نقطة الألم: تسلسل أنواع عميق و if-else مطول
يحتوي خط أنابيب البيانات الخاص ببوب على 3 مصادر بيانات (API/File/Database)، لكل منها تكوينات وطرق اتصال مختلفة. نفذها باستخدام الوراثة، لكن التسلسل كان عميقاً والكود كان مطولاً. عند تحليل استجابات API، كانت هناك حاجة لعدد كبير من فحوصات if-else لرموز الحالة وهياكل الاستجابة، مع كتابة 200 سطر من الكود الشرطي لـ 5 أنواع استجابة فقط.
(2) حل ميزات Dart 3 الجديدة
تقيد الفئات المختومة أنواع مصادر البيانات، وتعالج مطابقة الأنماط بمهارة الاستجابات المختلفة، وتوفر السجلات قيماً إرجاع متعددة خفيفة الوزن.
// صنف مختوم: أنواع مصادر بيانات شاملة
sealed class DataSource {}
class ApiSource extends DataSource { final String endpoint; ApiSource(this.endpoint); }
class FileSource extends DataSource { final String path; FileSource(this.path); }
// مطابقة أنماط على صنف مختوم - المترجم يضمن معالجة جميع الحالات
String describe(DataSource source) => switch (source) {
ApiSource(:final endpoint) => 'API: $endpoint',
FileSource(:final path) => 'File: $path',
};
// السجلات: إرجاع قيم متعددة
(String, double) parseResponse(String raw) => ('ORD-001', 1500.0);
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
(3) الفوائد
- تجعل الفئات المختومة فروع النوع متناهية وقابلة للعد؛ يضمن switch الشامل عدم وجود حالات مفقودة.
- تقلل مطابقة الأنماط من سلاسل if-else المطولة إلى تعابير switch تصريحية.
- تحل السجلات محل الفئات الصغيرة؛ قيم الإرجاع المتعددة لم تعد تتطلب مكتبة Tuple أو Map.
3. السجلات (Records)
(1) أساسيات السجلات
▶ مثال
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
: إنشاء واستخدام السجلات
void main() {
// سجل موضعي
var order = ('ORD-001', 1500.0, 'completed');
print(order.$1); // ORD-001
print(order.$2); // 1500.0
print(order.$3); // completed
// سجل مسمى (مفضل للقراءة)
var order2 = (id: 'ORD-002', amount: 3200.0, status: 'pending');
print(order2.id); // ORD-002
print(order2.amount); // 3200.0
print(order2.status); // pending
// مختلط موضعي ومسمى
var mixed = ('ORD-003', 890.0, category: 'Electronics');
print(mixed.$1); // ORD-003
print(mixed.category); // Electronics
}
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
▶ مثال
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
: السجلات كقيم إرجاع للدوال
// قيم إرجاع متعددة مع السجلات
({String id, double amount, double tax}) calculateOrder(double amount, double taxRate) {
return (
id: 'ORD-${DateTime.now().millisecondsSinceEpoch}',
amount: amount,
tax: amount * taxRate,
);
}
// سجل موضعي للتجميع السريع
(String, double) parseAmount(String input) {
final parts = input.split(':');
return (parts[0], double.parse(parts[1]));
}
void main() {
final order = calculateOrder(1500.0, 0.08);
print('ID: ${order.id}, Amount: \$${order.amount}, Tax: \$${order.tax}');
final (label, value) = parseAmount('Revenue:52500.75');
print('$label: \$$value USD');
}
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
| ميزة السجل | الصيغة | الوصف |
|---|---|---|
| حقول موضعية | .$1, .$2 |
الوصول حسب الموضع |
| حقول مسماة | .name |
الوصول حسب الاسم |
| تعليق النوع | (String, int) أو ({String name, int count}) |
نوع صريح |
| المساواة | مساواة القيمة | متساوية إذا كانت قيم الحقول متساوية |
4. مطابقة الأنماط
(1) أنواع أنماط المطابقة
▶ مثال
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
: أنماط المتغيرات والتفكيك
void main() {
// نمط متغير - استخراج القيم
var (id, amount, status) = ('ORD-001', 1500.0, 'completed');
print('ID: $id, Amount: $amount, Status: $status');
// تفكيك السجل مع الحقول المسماة
var (:id, :amount, :status) = (id: 'ORD-002', amount: 3200.0, status: 'pending');
print('ID: $id, Amount: $amount, Status: $status');
// تفكيك القائمة
var [first, second, ...rest] = [1, 2, 3, 4, 5];
print('First: $first, Second: $second, Rest: $rest');
// تفكيك الخريطة
var {'id': orderId, 'amount': orderAmount} = {'id': 'ORD-003', 'amount': 890.0};
print('Order: $orderId, Amount: $orderAmount');
}
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
▶ مثال
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
: نمط if-case
void main() {
Object value = '1,500.00 USD';
// مطابقة أنماط if-case
if (value case String s when s.contains('USD')) {
print('USD value: $s');
}
// مطابقة أنماط على السجل
var response = (statusCode: 200, body: '{"orders": 1500}');
if (response case (statusCode: 200, :var body)) {
print('Success response: $body');
}
// مطابقة أنماط على القائمة
var orders = ['ORD-001', 'ORD-002', 'ORD-003'];
if (orders case [var first, var second, ...]) {
print('First two: $first, $second');
}
}
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
▶ مثال
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
: نمط switch
String classifyOrder(Object value) => switch (value) {
// نمط النوع
int i when i > 1000 => 'High value integer: $i',
int i => 'Low value integer: $i',
double d when d >= 1000 => 'Premium: \$${d.toStringAsFixed(2)}',
double d => 'Standard: \$${d.toStringAsFixed(2)}',
String s => 'String: $s',
List l when l.length > 100 => 'Large batch: ${l.length} items',
List l => 'Small batch: ${l.length} items',
_ => 'Unknown type',
};
// مطابقة أنماط مع السجلات
String describeResponse((int, String) response) => switch (response) {
(200, var body) => 'OK: $body',
(404, _) => 'Not Found',
(500, var msg) => 'Server Error: $msg',
(>= 400, var msg) => 'Client Error: $msg',
_ => 'Unknown response',
};
void main() {
print(classifyOrder(1500)); // High value integer: 1500
print(classifyOrder(890.0)); // Standard: $890.00
print(classifyOrder([1, 2, 3])); // Small batch: 3 items
print(describeResponse((200, 'OK'))); // OK: OK
print(describeResponse((404, 'Missing'))); // Not Found
print(describeResponse((500, 'Crash'))); // Server Error: Crash
}
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
| نوع النمط | الصيغة | الاستخدام |
|---|---|---|
| نمط متغير | var x |
استخراج القيم |
| نمط نوع | Type x |
فحص النوع + الاستخراج |
| نمط ثابت | 42, 'hello' |
مطابقة تامة |
| نمط علائقي | >= 1000 |
فحص النطاق |
| نمط منطقي | a || b, a && b |
دمج الشروط |
| حارس | when condition |
شرط إضافي |
| بدل | _ |
تجاهل القيمة |
5. الفئات المختومة (Sealed Classes)
(1) تسلسل الفئة المختومة
classDiagram
class DataSource {
<<sealed>>
}
class ApiSource {
+String endpoint
+Map headers
}
class FileSource {
+String path
+Encoding encoding
}
class DatabaseSource {
+String connectionString
}
DataSource <|-- ApiSource
DataSource <|-- FileSource
DataSource <|-- DatabaseSource
note for DataSource "ضمان switch شامل"
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
▶ مثال
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
: تعريف الفئة المختومة والمطابقة الشاملة
sealed class DataSource {
const DataSource();
}
class ApiSource extends DataSource {
final String endpoint;
final Map<String, String> headers;
const ApiSource(this.endpoint, {this.headers = const {}});
}
class FileSource extends DataSource {
final String path;
const FileSource(this.path);
}
class DatabaseSource extends DataSource {
final String connectionString;
const DatabaseSource(this.connectionString);
}
// switch شامل - المترجم يفحص جميع الأنواع الفرعية
String describeSource(DataSource source) => switch (source) {
ApiSource(:final endpoint, :final headers) =>
'API: $endpoint (${headers.length} headers)',
FileSource(:final path) =>
'File: $path',
DatabaseSource(:final connectionString) =>
'Database: $connectionString',
};
// إذا أضفت نوعاً فرعياً جديداً ونسيت تحديث switch،
// سيبلغ المترجم عن خطأ!
void main() {
final sources = <DataSource>[
ApiSource('https://api.example.com/orders', headers: {'Authorization': 'Bearer token'}),
FileSource('/data/orders.csv'),
DatabaseSource('postgresql://localhost:5432/orders'),
];
for (final source in sources) {
print(describeSource(source));
}
}
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
| ميزة الفئة المختومة | الوصف |
|---|---|
| تقييد الفئة الفرعية | يجب تعريفها في نفس المكتبة |
| فحص الشمولية | يجب أن يغطي switch جميع الأنواع الفرعية |
| ضمان وقت الترجمة | switch المفقود سيسبب خطأ ترجمة عند إضافة فئة فرعية جديدة |
| لا يمكن إنشاء مثيل | مجردة ضمنياً |
6. الثلاثي في العمل
▶ مثال
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
: مزيج Sealed + Pattern + Records
// صنف مختوم لنتائج API
sealed class ApiResult<T> {
const ApiResult();
}
class Success<T> extends ApiResult<T> {
final T data;
final (int, String) metadata; // سجل: (statusCode, message)
const Success(this.data, this.metadata);
}
class ApiError<T> extends ApiResult<T> {
final String message;
final int? statusCode;
const ApiError(this.message, {this.statusCode});
}
class NetworkError<T> extends ApiResult<T> {
final String reason;
const NetworkError(this.reason);
}
// مطابقة الأنماط مع التفكيك
String handleResult(ApiResult<List<String>> result) => switch (result) {
Success(:final data, metadata: (200, final msg)) =>
'OK ($msg): ${data.length} items loaded',
Success(:final data, metadata: (final code, _)) =>
'Loaded with status $code: ${data.length} items',
ApiError(:final message, statusCode: final code?) =>
'API Error [$code]: $message',
ApiError(:final message) =>
'API Error: $message',
NetworkError(:final reason) =>
'Network Error: $reason',
};
void main() {
final results = <ApiResult<List<String>>>[
Success(['ORD-001', 'ORD-002'], (200, 'OK')),
Success(['ORD-003'], (206, 'Partial Content')),
ApiError('Rate limit exceeded', statusCode: 429),
ApiError('Unknown error'),
NetworkError('Connection timeout'),
];
for (final result in results) {
print(handleResult(result));
}
}
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
7. سيناريو بوب: مصادر بيانات خط أنابيب البيانات وتحليل النتائج
▶ مثال
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
: تجريد مصدر البيانات الكامل
sealed class DataSource {
const DataSource();
String get displayName;
}
class ApiSource extends DataSource {
final String endpoint;
final Duration timeout;
const ApiSource(this.endpoint, {this.timeout = const Duration(seconds: 10)});
@override
String get displayName => 'API ($endpoint)';
}
class FileSource extends DataSource {
final String path;
final String format;
const FileSource(this.path, {this.format = 'csv'});
@override
String get displayName => 'File ($path, $format)';
}
class DatabaseSource extends DataSource {
final String connectionString;
final String query;
const DatabaseSource(this.connectionString, {this.query = 'SELECT * FROM orders'});
@override
String get displayName => 'Database ($connectionString)';
}
// نتيجة المعالجة مع السجلات
typedef ProcessingResult = ({int processed, int skipped, double revenue, Duration time});
ProcessingResult processDataSource(DataSource source) => switch (source) {
ApiSource(:final endpoint, :final timeout) => (
processed: 50000,
skipped: 120,
revenue: 525000.0,
time: timeout,
),
FileSource(:final path, :final format) => (
processed: 100000,
skipped: 350,
revenue: 1200000.0,
time: Duration(seconds: 5),
),
DatabaseSource(:final connectionString, :final query) => (
processed: 1200000,
skipped: 800,
revenue: 15000000.0,
time: Duration(seconds: 15),
),
};
void main() {
final sources = <DataSource>[
ApiSource('https://api.example.com/orders'),
FileSource('/data/orders.csv'),
DatabaseSource('postgresql://localhost:5432/orders'),
];
print('=== DataPipeline Source Analysis ===');
for (final source in sources) {
final (:processed, :skipped, :revenue, :time) = processDataSource(source);
print('\n${source.displayName}:');
print(' Processed: ${processed} orders');
print(' Skipped: $skipped records');
print(' Revenue: \$${revenue.toStringAsFixed(2)} USD');
print(' Time: ${time.inSeconds}s');
}
}
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
8. المثال الكامل: نظام معالجة استجابة خط أنابيب البيانات
// ============================================
// نظام معالجة استجابة خط أنابيب البيانات
// فئات مختومة + مطابقة أنماط + سجلات
// ============================================
// صنف مختوم لأنواع الاستجابة المختلفة
sealed class ApiResponse {
const ApiResponse();
}
class SuccessResponse extends ApiResponse {
final int statusCode;
final Map<String, dynamic> data;
const SuccessResponse(this.statusCode, this.data);
}
class ErrorResponse extends ApiResponse {
final int statusCode;
final String message;
final String? details;
const ErrorResponse(this.statusCode, this.message, {this.details});
}
class TimeoutResponse extends ApiResponse {
final Duration timeout;
final String endpoint;
const TimeoutResponse(this.timeout, this.endpoint);
}
class RedirectResponse extends ApiResponse {
final String newEndpoint;
final int statusCode;
const RedirectResponse(this.newEndpoint, this.statusCode);
}
// نتيجة المعالجة كسجل
typedef Outcome = ({bool success, String message, double? revenue});
// مطابقة أنماط على جميع أنواع الاستجابة - شامل!
Outcome handleResponse(ApiResponse response) => switch (response) {
SuccessResponse(statusCode: 200, :final data) when data.containsKey('orders') => (
success: true,
message: 'Loaded ${data['orders']} orders',
revenue: (data['revenue'] as num?)?.toDouble(),
),
SuccessResponse(statusCode: 200, :final data) => (
success: true,
message: 'OK but no orders field: ${data.keys}',
revenue: null,
),
SuccessResponse(statusCode: 206, :final data) => (
success: true,
message: 'Partial data: ${data.length} fields',
revenue: null,
),
SuccessResponse(statusCode: final code) => (
success: true,
message: 'Unexpected success code: $code',
revenue: null,
),
ErrorResponse(statusCode: 429, :final message) => (
success: false,
message: 'Rate limited: $message',
revenue: null,
),
ErrorResponse(statusCode: final code, :final message, :final details?) => (
success: false,
message: 'Error [$code]: $message - $details',
revenue: null,
),
ErrorResponse(statusCode: final code, :final message) => (
success: false,
message: 'Error [$code]: $message',
revenue: null,
),
TimeoutResponse(:final timeout, :final endpoint) => (
success: false,
message: 'Timeout after ${timeout.inSeconds}s on $endpoint',
revenue: null,
),
RedirectResponse(:final newEndpoint, :final statusCode) => (
success: false,
message: 'Redirect ($statusCode) to $newEndpoint',
revenue: null,
),
};
void main() {
final responses = <ApiResponse>[
SuccessResponse(200, {'orders': 50000, 'revenue': 525000.0}),
SuccessResponse(200, {'products': 3000}),
SuccessResponse(206, {'partial': true}),
ErrorResponse(429, 'Rate limit exceeded'),
ErrorResponse(500, 'Internal server error', details: 'Database connection lost'),
ErrorResponse(404, 'Not found'),
TimeoutResponse(const Duration(seconds: 30), '/api/v1/orders'),
RedirectResponse('/api/v2/orders', 301),
];
print('=== DataPipeline Response Handler ===\n');
for (final response in responses) {
final (:success, :message, :revenue) = handleResponse(response);
final status = success ? 'OK' : 'FAIL';
final rev = revenue != null ? ' (\$${revenue.toStringAsFixed(2)} USD)' : '';
print('[$status] $message$rev');
}
}
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً حسب إصدار SDK.
الإخراج:
=== DataPipeline Response Handler ===
[OK] Loaded 50000 orders ($525000.00 USD)
[OK] OK but no orders field: (products)
[OK] Partial data: 1 fields
[FAIL] Rate limited: Rate limit exceeded
[FAIL] Error [500]: Internal server error - Database connection lost
[FAIL] Error [404]: Not found
[FAIL] Timeout after 30s on /api/v1/orders
[FAIL] Redirect (301) to /api/v2/orders
❓ أسئلة شائعة
س: ما الفرق بين السجلات والفئات؟ ج: السجلات هياكل بيانات مجهولة وخفيفة الوزن قائمة على القيمة؛ الفئات لها أسماء، قائمة على المرجع، ويمكن أن يكون لها طرق. السجلات مناسبة لتمرير البيانات البسيطة، بينما الفئات لمنطق الأعمال المعقد.
س: ما الفرق بين الفئة المختومة والفئة المجردة؟ ج: تقيد الفئات المختومة الفئات الفرعية بنفس المكتبة، مما يتيح للمترجم إجراء فحوصات شاملة؛ يمكن أن يكون للفئات المجردة فئات فرعية في أي مكان، مما يمنع الشمولية. استخدم sealed عندما تحتاج إلى ضمانات شاملة.
س: أين يمكن استخدام مطابقة الأنماط؟ ج: تعابير switch، جمل switch، if-case، إعلانات المتغيرات، حلقات for-in. في Dart 3، الأنماط في كل مكان.
س: ما أداء السجلات؟ ج: تُترجم السجلات إلى كائنات عادية، بأداء مماثل للفئات الصغيرة. لا يوجد عبء إضافي، لكنها ليست أيضاً أسرع من الفئات. اختر بناءً على قابلية القراءة.
س: هل يمكن أن يكون للفئة المختومة مُنشئ؟ ج: يمكن أن يكون لها مُنشئات مصنع (تُرجع فئات فرعية)، لكن الفئة نفسها لا يمكن إنشاء مثيل لها (مجردة ضمنياً). المُنشئات مخصصة بشكل أساسي لمشاركة منطق التهيئة بين الفئات الفرعية.
س: هل هناك فرق بين
_وdefaultفي تعبير switch؟ ج: وظيفياً، إنهما متطابقان - كلاهما مطابق شامل. يوصي Dart 3 باستخدام_(نمط البدل) للإيجاز.defaultهو الصياغة القديمة المحتفظ بها للتوافق.
س: هل يمكن استخدام السجلات كمفاتيح لـ Map؟ ج: نعم. السجلات قائمة على مساواة القيمة وتنفذ تلقائياً
==وhashCode.(1, 2) == (1, 2)صحيح.
📖 ملخص
- السجلات هي تجميع بيانات مجهول خفيف الوزن، مع دعم للحقول الموضعية والمسماة، والمساواة القائمة على القيمة.
- تدعم مطابقة الأنماط if-case، و switch-pattern، وتفكيك المتغيرات، وشروط الحارس.
- تقيد الفئات المختومة الفئات الفرعية بنفس المكتبة؛ يضمن المترجم switch شامل.
- الثلاثي في العمل: Sealed يحدد نوعاً متناهياً ← Pattern يطابق ويفكك ← Records تقدم قيماً إرجاع خفيفة الوزن.
- هذا هو الترقية الأساسية الأكثر في Dart 3، مما يجعل الكود أكثر أماناً وإيجازاً وتعبيرية.
📝 تمارين
- أساسي (⭐): عرّف سجلين: سجل موضعي
(String, double)يمثل معرف الطلب والمبلغ، وسجل مسمى({String id, double amount, String status}). أنشئ مثيلات وفكك/اطبعها. - متوسط (⭐⭐): استخدم صنف مختوم لتعريف 3 طرق دفع (CreditCard/PayPal/BankTransfer)، لكل منها حقول مختلفة. نفّذ طريقة
describePayment()باستخدام تعبير switch، مع ضمان الشمولية. - تحدي (⭐⭐⭐): صمم نظام معالجة استجابة API كاملاً: استخدم صنف مختوم لتعريف أنواع الاستجابة (Success/ValidationError/ServerError/Timeout)، كل منها يحمل بيانات سجل مختلفة. نفّذ معالج استجابة باستخدام مطابقة الأنماط، مع دمج الأنماط العلائقية وشروط الحارس.